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  custom_log_level.reset();
100 
101  return active;
102  }
103 
105  virtual bool begin(const char* urlStr, const char* acceptMime,
106  MethodID action, const char* reqMime, Stream& reqData,
107  int len = -1) {
108  LOGI("%s: %s", LOG_METHOD, urlStr);
109  if (!preProcess(urlStr, acceptMime)) {
110  LOGE("preProcess failed");
111  return false;
112  }
113  int result = process<Stream&>(action, url, reqMime, reqData, len);
114  if (result > 0) {
115  size = request.contentLength();
116  LOGI("size: %d", (int)size);
117  if (size >= 0 && wait_for_data) {
118  waitForData(clientTimeout);
119  }
120  }
121  total_read = 0;
122  active = result == 200;
123  LOGI("==> http status: %d", result);
124  custom_log_level.reset();
125 
126  return active;
127  }
128 
129  virtual void end() override {
130  if (active) request.stop();
131  active = false;
132  clear();
133  }
134 
135  virtual int available() override {
136  if (!active || !request) return 0;
137 
138  int result = request.available();
139  LOGD("available: %d", result);
140  return result;
141  }
142 
143  virtual size_t readBytes(uint8_t* data, size_t len) override {
144  if (!active || !request) return 0;
145 
146  int read = request.read((uint8_t*)&data[0], len);
147  if (read < 0) {
148  read = 0;
149  }
150  total_read += read;
151  LOGD("readBytes %d -> %d", (int)len, read);
152  return read;
153  }
154 
155  virtual int read() override {
156  if (!active) return -1;
157  // lazy allocation since this is rarely used
158  read_buffer.resize(read_buffer_size);
159 
160  fillBuffer();
161  total_read++;
162  return isEOS() ? -1 : read_buffer[read_pos++];
163  }
164 
165  virtual int peek() override {
166  if (!active) return -1;
167  // lazy allocation since this is rarely used
168  read_buffer.resize(read_buffer_size);
169 
170  fillBuffer();
171  return isEOS() ? -1 : read_buffer[read_pos];
172  }
173 
174  virtual void flush() override {}
175 
176  virtual size_t write(uint8_t) override { return not_supported(0); }
177 
178  virtual size_t write(const uint8_t*, size_t len) override {
179  return not_supported(0);
180  }
181 
183  virtual HttpRequest& httpRequest() override { return request; }
184 
185  operator bool() { return active && request.isReady(); }
186 
188  virtual void setTimeout(int ms) { clientTimeout = ms; }
189 
192  void setPowerSave(bool ps) { is_power_save = ps; }
193 
195  void setAutoCreateLines(bool flag) {
196  httpRequest().reply().setAutoCreateLines(flag);
197  }
198 
200  void setConnectionClose(bool close) {
201  httpRequest().setConnection(close ? CON_CLOSE : CON_KEEP_ALIVE);
202  }
203 
205  void clear() {
206  httpRequest().reply().clear();
207  httpRequest().header().clear();
208  read_buffer.resize(0);
209  read_pos = 0;
210  read_size = 0;
211  }
212 
214  void addRequestHeader(const char* header, const char* value) {
215  request.header().put(header, value);
216  }
217 
219  void setOnConnectCallback(void (*callback)(
220  HttpRequest& request, Url& url, HttpRequestHeader& request_header)) {
221  request.setOnConnectCallback(callback);
222  }
223 
224  void setWaitForData(bool flag) { wait_for_data = flag; }
225 
226  int contentLength() { return size; }
227 
228  size_t totalRead() { return total_read; }
229 
231  virtual bool waitForData(int timeout) {
232  TRACED();
233  uint32_t end = millis() + timeout;
234  if (request.available() == 0) {
235  LOGI("Request written ... waiting for reply");
236  while (request.available() == 0) {
237  if (millis() > end) break;
238  // stop waiting if we got an error
239  if (request.reply().statusCode() >= 300) {
240  LOGE("Error code recieved ... stop waiting for reply");
241  break;
242  }
243  delay(500);
244  }
245  }
246  LOGD("available: %d", request.available());
247  return request.available() > 0;
248  }
249 
251  void setLogLevel(AudioLogger::LogLevel level) { custom_log_level.set(level); }
252 
253  const char* urlStr() { return url_str.c_str(); }
254 
255  protected:
256  HttpRequest request;
257  CustomLogLevel custom_log_level;
258  Str url_str;
259  Url url;
260  long size;
261  long total_read;
262  // buffered single byte read
263  Vector<uint8_t> read_buffer{0};
264  uint16_t read_buffer_size = DEFAULT_BUFFER_SIZE;
265  uint16_t read_pos;
266  uint16_t read_size;
267  bool active = false;
268  bool wait_for_data = true;
269  // optional
270  const char* network = nullptr;
271  const char* password = nullptr;
272  Client* client = nullptr; // client defined via setClient
273 #ifdef USE_WIFI
274  WiFiClient* clientInsecure = nullptr; // wifi client for http
275 #endif
276 #ifdef USE_WIFI_CLIENT_SECURE
277  WiFiClientSecure* clientSecure = nullptr; // wifi client for https
278 #endif
279  int clientTimeout = URL_CLIENT_TIMEOUT; // 60000;
280  unsigned long handshakeTimeout = URL_HANDSHAKE_TIMEOUT; // 120000
281  bool is_power_save = false;
282 
283  bool preProcess(const char* urlStr, const char* acceptMime) {
284  TRACED();
285  custom_log_level.set();
286  url_str = urlStr;
287  url.setUrl(url_str.c_str());
288  int result = -1;
289 
290  // close it - if we have an active connection
291  if (active) end();
292 
293 #ifdef USE_WIFI
294  // optional: login if necessary if no external client is defined
295  if (client == nullptr){
296  if (!login()){
297  LOGE("Not connected");
298  return false;
299  }
300  }
301 
302 #endif
303  // request.reply().setAutoCreateLines(false);
304  if (acceptMime != nullptr) {
305  request.setAcceptMime(acceptMime);
306  }
307 
308  // setup client
309  Client& client = getClient(url.isSecure());
310  request.setClient(client);
311 
312  // set timeout
313  client.setTimeout(clientTimeout / 1000);
314  request.setTimeout(clientTimeout);
315 
316 #ifdef ESP32
317  // There is a bug in IDF 4!
318  if (clientSecure != nullptr) {
319  clientSecure->setHandshakeTimeout(handshakeTimeout);
320  }
321 
322  // Performance optimization for ESP32
323  if (!is_power_save) {
324  esp_wifi_set_ps(WIFI_PS_NONE);
325  }
326 #endif
327  return true;
328  }
329 
331  template <typename T>
332  int process(MethodID action, Url& url, const char* reqMime, T reqData,
333  int len = -1) {
334  TRACED();
335  // keep icy across redirect requests ?
336  const char* icy = request.header().get("Icy-MetaData");
337 
338  int status_code = request.process(action, url, reqMime, reqData, len);
339  // redirect
340  while (request.reply().isRedirectStatus()) {
341  const char* redirect_url = request.reply().get(LOCATION);
342  if (redirect_url != nullptr) {
343  LOGW("Redirected to: %s", redirect_url);
344  url.setUrl(redirect_url);
345  Client* p_client = &getClient(url.isSecure());
346  p_client->stop();
347  request.setClient(*p_client);
348  if (icy) {
349  request.header().put("Icy-MetaData", icy);
350  }
351  status_code = request.process(action, url, reqMime, reqData, len);
352  } else {
353  LOGE("Location is null");
354  break;
355  }
356  }
357  return status_code;
358  }
359 
361  Client& getClient(bool isSecure) {
362 #ifdef USE_WIFI_CLIENT_SECURE
363  if (isSecure) {
364  if (clientSecure == nullptr) {
365  clientSecure = new WiFiClientSecure();
366  clientSecure->setInsecure();
367  }
368  LOGI("WiFiClientSecure");
369  return *clientSecure;
370  }
371 #endif
372 #ifdef USE_WIFI
373  if (clientInsecure == nullptr) {
374  clientInsecure = new WiFiClient();
375  LOGI("WiFiClient");
376  }
377  return *clientInsecure;
378 #else
379  if (client == nullptr){
380  LOGE("Client not set");
381  stop();
382  }
383  return *client; // to avoid compiler warning
384 #endif
385  }
386 
387  inline void fillBuffer() {
388  if (isEOS()) {
389  // if we consumed all bytes we refill the buffer
390  read_size = readBytes(&read_buffer[0], read_buffer_size);
391  read_pos = 0;
392  }
393  }
394 
395  inline bool isEOS() { return read_pos >= read_size; }
396 
397  bool login() {
398 #ifdef USE_WIFI
399  if (network != nullptr && password != nullptr &&
400  WiFi.status() != WL_CONNECTED) {
401  TRACEI();
402  WiFi.begin(network, password);
403  while (WiFi.status() != WL_CONNECTED) {
404  Serial.print(".");
405  delay(500);
406  }
407  Serial.println();
408  delay(10);
409  return WiFi.status() == WL_CONNECTED;
410  }
411  return WiFi.status() == WL_CONNECTED;
412 #else
413  return false;
414 #endif
415  }
416 };
417 
418 } // namespace audio_tools
419 
420 #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:143
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
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
void setLogLevel(AudioLogger::LogLevel level)
Defines the class specific custom log level.
Definition: URLStream.h:251
virtual HttpRequest & httpRequest() override
provides access to the HttpRequest
Definition: URLStream.h:183
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:188
void setPowerSave(bool ps)
Definition: URLStream.h:192
Client & getClient(bool isSecure)
Determines the client.
Definition: URLStream.h:361
int process(MethodID action, Url &url, const char *reqMime, T reqData, int len=-1)
Process the Http request and handle redirects.
Definition: URLStream.h:332
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:105
virtual bool waitForData(int timeout)
waits for some data - returns false if the request has failed
Definition: URLStream.h:231
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:219
void clear()
Releases the memory from the request and reply.
Definition: URLStream.h:205
void setAutoCreateLines(bool flag)
If set to true, new undefined reply parameters will be stored.
Definition: URLStream.h:195
void addRequestHeader(const char *header, const char *value)
Adds/Updates a request header.
Definition: URLStream.h:214
void setConnectionClose(bool close)
Sets if the connection should be close automatically.
Definition: URLStream.h:200
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:821
uint32_t millis()
Returns the milliseconds since the start.
Definition: Time.h:12