arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
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 "AudioTools/CoreAudio/AudioBasic/Str.h"
14#include "AudioTools/CoreAudio/AudioHttp/AbstractURLStream.h"
15#include "AudioTools/CoreAudio/AudioHttp/HttpRequest.h"
16#include "AudioTools/CoreAudio/AudioHttp/ICYStreamT.h"
17#include "AudioTools/CoreAudio/AudioHttp/URLStreamBufferedT.h"
18
19
20namespace audio_tools {
21
33 public:
34 URLStream(int readBufferSize = DEFAULT_BUFFER_SIZE) {
35 TRACED();
37 }
38
39 URLStream(Client& clientPar, int readBufferSize = DEFAULT_BUFFER_SIZE) {
40 TRACED();
43 }
44
45 URLStream(const char* network, const char* password,
46 int readBufferSize = DEFAULT_BUFFER_SIZE) {
47 TRACED();
49 setSSID(network);
50 setPassword(password);
51 }
52
53 URLStream(const URLStream&) = delete;
54
55 ~URLStream() {
56 TRACED();
57 end();
58#ifdef USE_WIFI_CLIENT_SECURE
59 if (clientSecure != nullptr) {
60 delete clientSecure;
61 clientSecure = nullptr;
62 }
63#endif
64#ifdef USE_WIFI
65 if (clientInsecure != nullptr) {
66 delete clientInsecure;
67 clientInsecure = nullptr;
68 }
69#endif
70 }
71
73 void setClient(Client& clientPar) override { client = &clientPar; }
74
76 void setSSID(const char* ssid) override { this->network = ssid; }
77
79 void setPassword(const char* password) override { this->password = password; }
80
83 read_buffer_size = readBufferSize;
84 }
85
87 virtual bool begin(const char* urlStr, const char* acceptMime = nullptr,
88 MethodID action = GET, const char* reqMime = "",
89 const char* reqData = "") override {
90 LOGI("%s: %s", LOG_METHOD, urlStr);
91 if (!preProcess(urlStr, acceptMime)) {
92 LOGE("preProcess failed");
93 return false;
94 }
95 int result = process<const char*>(action, url, reqMime, reqData);
96 if (result > 0) {
97 size = request.contentLength();
98 LOGI("contentLength: %d", (int)size);
99 if (size >= 0 && wait_for_data) {
100 waitForData(clientTimeout);
101 }
102 }
103 total_read = 0;
104 active = result == 200;
105 LOGI("==> http status: %d", result);
106#if USE_AUDIO_LOGGING && !defined(USE_IDF_LOGGER)
107 custom_log_level.reset();
108#endif
109 return active;
110 }
111
113 virtual bool begin(const char* urlStr, const char* acceptMime,
114 MethodID action, const char* reqMime, Stream& reqData,
115 int len = -1) {
116 LOGI("%s: %s", LOG_METHOD, urlStr);
117 if (!preProcess(urlStr, acceptMime)) {
118 LOGE("preProcess failed");
119 return false;
120 }
121 int result = process<Stream&>(action, url, reqMime, reqData, len);
122 if (result > 0) {
123 size = request.contentLength();
124 LOGI("size: %d", (int)size);
125 if (size >= 0 && wait_for_data) {
126 waitForData(clientTimeout);
127 }
128 }
129 total_read = 0;
130 active = result == 200;
131 LOGI("==> http status: %d", result);
132#if USE_AUDIO_LOGGING && !defined(USE_IDF_LOGGER)
133 custom_log_level.reset();
134#endif
135 return active;
136 }
137
138 virtual void end() override {
139 if (active) request.stop();
140 active = false;
141 clear();
142 }
143
144 virtual int available() override {
145 if (!active) return 0;
146
147 int result = request.available();
148 LOGD("available: %d", result);
149 return result;
150 }
151
152 virtual size_t readBytes(uint8_t* data, size_t len) override {
153 if (!active) return 0;
154
155 int read = request.read((uint8_t*)&data[0], len);
156 if (read < 0) {
157 read = 0;
158 }
159 total_read += read;
160 LOGD("readBytes %d -> %d", (int)len, read);
161 return read;
162 }
163
164 virtual int read() override {
165 if (!active) return -1;
166 // lazy allocation since this is rarely used
167 read_buffer.resize(read_buffer_size);
168
169 fillBuffer();
170 total_read++;
171 return isEOS() ? -1 : read_buffer[read_pos++];
172 }
173
174 virtual int peek() override {
175 if (!active) return -1;
176 // lazy allocation since this is rarely used
177 read_buffer.resize(read_buffer_size);
178
179 fillBuffer();
180 return isEOS() ? -1 : read_buffer[read_pos];
181 }
182
183 virtual void flush() override {}
184
185 virtual size_t write(uint8_t) override { return not_supported(0); }
186
187 virtual size_t write(const uint8_t*, size_t len) override {
188 return not_supported(0);
189 }
190
192 virtual HttpRequest& httpRequest() override { return request; }
193
194 operator bool() { return active && request.isReady(); }
195
197 virtual void setTimeout(int ms) { clientTimeout = ms; }
198
201 void setPowerSave(bool ps) { is_power_save = ps; }
202
205 httpRequest().reply().setAutoCreateLines(flag);
206 }
207
209 void setConnectionClose(bool close) {
210 httpRequest().setConnection(close ? CON_CLOSE : CON_KEEP_ALIVE);
211 }
212
214 void clear() {
215 httpRequest().reply().clear();
216 httpRequest().header().clear();
217 read_buffer.resize(0);
218 read_pos = 0;
219 read_size = 0;
220 }
221
223 void addRequestHeader(const char* key, const char* value) {
224 request.header().put(key, value);
225 }
226
227 const char* getReplyHeader(const char* key){
228 return request.reply().get(key);
229 }
230
232 void setOnConnectCallback(void (*callback)(
233 HttpRequest& request, Url& url, HttpRequestHeader& request_header)) {
234 request.setOnConnectCallback(callback);
235 }
236
237 void setWaitForData(bool flag) { wait_for_data = flag; }
238
239 int contentLength() { return size; }
240
241 size_t totalRead() { return total_read; }
242
244 virtual bool waitForData(int timeout) {
245 TRACED();
246 uint32_t end = millis() + timeout;
247 if (request.available() == 0) {
248 LOGI("Request written ... waiting for reply");
249 while (request.available() == 0) {
250 if (millis() > end) break;
251 // stop waiting if we got an error
252 if (request.reply().statusCode() >= 300) {
253 LOGE("Error code recieved ... stop waiting for reply");
254 break;
255 }
256 delay(500);
257 }
258 }
259 LOGD("available: %d", request.available());
260 return request.available() > 0;
261 }
262
263#if USE_AUDIO_LOGGING && !defined(USE_IDF_LOGGER)
265 void setLogLevel(AudioLogger::LogLevel level) { custom_log_level.set(level); }
266#endif
267 const char* urlStr() { return url_str.c_str(); }
268
270 void setCACert(const char* cert){
271 #ifdef USE_WIFI_CLIENT_SECURE
272 if (clientSecure!=nullptr) clientSecure->setCACert(cert);
273 #endif
274 }
275
276 protected:
277 HttpRequest request;
278#if USE_AUDIO_LOGGING && !defined(USE_IDF_LOGGER)
279 CustomLogLevel custom_log_level;
280#endif
281 Str url_str;
282 Url url;
283 long size;
284 long total_read;
285 // buffered single byte read
286 Vector<uint8_t> read_buffer{0};
287 uint16_t read_buffer_size = DEFAULT_BUFFER_SIZE;
288 uint16_t read_pos;
289 uint16_t read_size;
290 bool active = false;
291 bool wait_for_data = true;
292 // optional
293 const char* network = nullptr;
294 const char* password = nullptr;
295 Client* client = nullptr; // client defined via setClient
296#ifdef USE_WIFI
297 WiFiClient* clientInsecure = nullptr; // wifi client for http
298#endif
299#ifdef USE_WIFI_CLIENT_SECURE
300 WiFiClientSecure* clientSecure = nullptr; // wifi client for https
301#endif
302 int clientTimeout = URL_CLIENT_TIMEOUT; // 60000;
303 unsigned long handshakeTimeout = URL_HANDSHAKE_TIMEOUT; // 120000
304 bool is_power_save = false;
305
306 bool preProcess(const char* urlStr, const char* acceptMime) {
307 TRACED();
308#if USE_AUDIO_LOGGING && !defined(USE_IDF_LOGGER)
309 custom_log_level.set();
310#endif
311 url_str = urlStr;
312 url.setUrl(url_str.c_str());
313 int result = -1;
314
315 // close it - if we have an active connection
316 if (active) end();
317
318#ifdef USE_WIFI
319 // optional: login if necessary if no external client is defined
320 if (client == nullptr){
321 if (!login()){
322 LOGE("Not connected");
323 return false;
324 }
325 }
326#endif
327
328 // request.reply().setAutoCreateLines(false);
329 if (acceptMime != nullptr) {
330 request.setAcceptMime(acceptMime);
331 }
332
333 // setup client
334 Client& client = getClient(url.isSecure());
335 request.setClient(client);
336
337 // set timeout
338 client.setTimeout(clientTimeout / 1000);
339 request.setTimeout(clientTimeout);
340
341#if defined(ESP32) && defined(USE_WIFI_CLIENT_SECURE)
342 // There is a bug in IDF 4!
343 if (clientSecure != nullptr) {
344 clientSecure->setHandshakeTimeout(handshakeTimeout);
345 }
346
347 // Performance optimization for ESP32
348 if (!is_power_save) {
349 esp_wifi_set_ps(WIFI_PS_NONE);
350 }
351#endif
352
353 return true;
354 }
355
357 template <typename T>
358 int process(MethodID action, Url& url, const char* reqMime, T reqData,
359 int len = -1) {
360 TRACED();
361 // keep icy across redirect requests ?
362 const char* icy = request.header().get("Icy-MetaData");
363
364 int status_code = request.process(action, url, reqMime, reqData, len);
365 // redirect
366 while (request.reply().isRedirectStatus()) {
367 const char* redirect_url = request.reply().get(LOCATION);
368 if (redirect_url != nullptr) {
369 LOGW("Redirected to: %s", redirect_url);
370 url.setUrl(redirect_url);
371 Client* p_client = &getClient(url.isSecure());
372 p_client->stop();
373 request.setClient(*p_client);
374 if (icy) {
375 request.header().put("Icy-MetaData", icy);
376 }
377 status_code = request.process(action, url, reqMime, reqData, len);
378 } else {
379 LOGE("Location is null");
380 break;
381 }
382 }
383 return status_code;
384 }
385
387 Client& getClient(bool isSecure) {
388#ifdef USE_WIFI_CLIENT_SECURE
389 if (isSecure) {
390 if (clientSecure == nullptr) {
391 clientSecure = new WiFiClientSecure();
392 clientSecure->setInsecure();
393 }
394 LOGI("WiFiClientSecure");
395 return *clientSecure;
396 }
397#endif
398#ifdef USE_WIFI
399 if (clientInsecure == nullptr) {
400 clientInsecure = new WiFiClient();
401 LOGI("WiFiClient");
402 }
403 return *clientInsecure;
404#else
405 if (client == nullptr){
406 LOGE("Client not set");
407 stop();
408 }
409 return *client; // to avoid compiler warning
410#endif
411 }
412
413 inline void fillBuffer() {
414 if (isEOS()) {
415 // if we consumed all bytes we refill the buffer
416 read_size = readBytes(&read_buffer[0], read_buffer_size);
417 read_pos = 0;
418 }
419 }
420
421 inline bool isEOS() { return read_pos >= read_size; }
422
423 bool login() {
424#ifdef USE_WIFI
425 if (network != nullptr && password != nullptr &&
426 WiFi.status() != WL_CONNECTED) {
427 TRACEI();
428 WiFi.begin(network, password);
429 while (WiFi.status() != WL_CONNECTED) {
430 Serial.print(".");
431 delay(500);
432 }
433 Serial.println();
434 delay(10);
435 return WiFi.status() == WL_CONNECTED;
436 }
437 return WiFi.status() == WL_CONNECTED;
438#else
439 return false;
440#endif
441 }
442};
443
444using ICYStream = ICYStreamT<URLStream>;
445
446#if defined(USE_CONCURRENCY)
447using URLStreamBuffered = URLStreamBufferedT<URLStream>;
448using ICYStreamBuffered = URLStreamBufferedT<ICYStream>;
449#endif
450
451} // namespace audio_tools
452
453#endif
Abstract Base class for all URLStream implementations.
Definition AbstractURLStream.h:17
Definition NoArduino.h:167
Reading and writing of Http Requests.
Definition HttpHeader.h:385
Simple API to process get, put, post, del http requests I tried to use Arduino HttpClient,...
Definition HttpRequest.h:25
void setTimeout(int timeoutMs)
Defines the client timeout in ms.
Definition HttpRequest.h:338
void setOnConnectCallback(void(*callback)(HttpRequest &request, Url &url, HttpRequestHeader &request_header))
Callback which allows you to add additional paramters dynamically.
Definition HttpRequest.h:332
bool isReady()
Definition HttpRequest.h:188
virtual HttpRequestHeader & header()
provides access to the request header
Definition HttpRequest.h:160
Str which keeps the data on the heap. We grow the allocated memory only if the copy source is not fit...
Definition Str.h:24
virtual const char * c_str()
provides the string value as const char*
Definition StrView.h:379
Definition NoArduino.h:142
Represents the content of a URL as Stream. We use the WiFi.h API. If you run into performance issues,...
Definition URLStream.h:32
void addRequestHeader(const char *key, const char *value)
Adds/Updates a request header.
Definition URLStream.h:223
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:87
virtual void setTimeout(int ms)
Defines the client timeout.
Definition URLStream.h:197
void setReadBufferSize(int readBufferSize)
Defines the buffer that is used by individual read() or peek() calls.
Definition URLStream.h:82
void setPowerSave(bool ps)
Definition URLStream.h:201
Client & getClient(bool isSecure)
Determines the client.
Definition URLStream.h:387
int process(MethodID action, Url &url, const char *reqMime, T reqData, int len=-1)
Process the Http request and handle redirects.
Definition URLStream.h:358
void setCACert(const char *cert)
Define the Root PEM Certificate for SSL.
Definition URLStream.h:270
void setSSID(const char *ssid) override
Sets the ssid that will be used for logging in (when calling begin)
Definition URLStream.h:76
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:113
virtual bool waitForData(int timeout)
waits for some data - returns false if the request has failed
Definition URLStream.h:244
void setClient(Client &clientPar) override
(Re-)defines the client
Definition URLStream.h:73
void setOnConnectCallback(void(*callback)(HttpRequest &request, Url &url, HttpRequestHeader &request_header))
Callback which allows you to add additional paramters dynamically.
Definition URLStream.h:232
void clear()
Releases the memory from the request and reply.
Definition URLStream.h:214
void setAutoCreateLines(bool flag)
If set to true, new undefined reply parameters will be stored.
Definition URLStream.h:204
void setConnectionClose(bool close)
Sets if the connection should be close automatically.
Definition URLStream.h:209
void setPassword(const char *password) override
Sets the password that will be used for logging in (when calling begin)
Definition URLStream.h:79
virtual HttpRequest & httpRequest() override
provides access to the HttpRequest
Definition URLStream.h:192
const char * getReplyHeader(const char *key)
Provides reply header information.
Definition URLStream.h:227
URL parser which breaks a full url string up into its individual parts.
Definition Url.h:22
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
void stop()
Public generic methods.
Definition AudioRuntime.h:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioConfig.h:885
uint32_t millis()
Returns the milliseconds since the start.
Definition Time.h:12