arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
URLStream.h
1#pragma once
2
3#include "AudioToolsConfig.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();
36 setReadBufferSize(readBufferSize);
37 }
38
39 URLStream(Client& clientPar, int readBufferSize = DEFAULT_BUFFER_SIZE) {
40 TRACED();
41 setReadBufferSize(readBufferSize);
42 setClient(clientPar);
43 }
44
45 URLStream(const char* network, const char* password,
46 int readBufferSize = DEFAULT_BUFFER_SIZE) {
47 TRACED();
48 setReadBufferSize(readBufferSize);
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
82 void setReadBufferSize(int readBufferSize) {
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 return active;
107 }
108
110 virtual bool begin(const char* urlStr, const char* acceptMime,
111 MethodID action, const char* reqMime, Stream& reqData,
112 int len = -1) {
113 LOGI("%s: %s", LOG_METHOD, urlStr);
114 if (!preProcess(urlStr, acceptMime)) {
115 LOGE("preProcess failed");
116 return false;
117 }
118 int result = process<Stream&>(action, url, reqMime, reqData, len);
119 if (result > 0) {
120 size = request.contentLength();
121 LOGI("size: %d", (int)size);
122 if (size >= 0 && wait_for_data) {
123 waitForData(clientTimeout);
124 }
125 }
126 total_read = 0;
127 active = result == 200;
128 LOGI("==> http status: %d", result);
129 return active;
130 }
131
132 virtual void end() override {
133 if (active) request.stop();
134 active = false;
135 clear();
136 }
137
138 virtual int available() override {
139 if (!active) return 0;
140
141 int result = request.available();
142 LOGD("available: %d", result);
143 return result;
144 }
145
146 virtual size_t readBytes(uint8_t* data, size_t len) override {
147 if (!active) return 0;
148
149 int read = request.read((uint8_t*)&data[0], len);
150 if (read < 0) {
151 read = 0;
152 }
153 total_read += read;
154 LOGD("readBytes %d -> %d", (int)len, read);
155 return read;
156 }
157
158 virtual int read() override {
159 if (!active) return -1;
160 // lazy allocation since this is rarely used
161 read_buffer.resize(read_buffer_size);
162
163 fillBuffer();
164 total_read++;
165 return isEOS() ? -1 : read_buffer[read_pos++];
166 }
167
168 virtual int peek() override {
169 if (!active) return -1;
170 // lazy allocation since this is rarely used
171 read_buffer.resize(read_buffer_size);
172
173 fillBuffer();
174 return isEOS() ? -1 : read_buffer[read_pos];
175 }
176
177 virtual void flush() override {}
178
179 virtual size_t write(uint8_t) override { return not_supported(0); }
180
181 virtual size_t write(const uint8_t*, size_t len) override {
182 return not_supported(0);
183 }
184
186 virtual HttpRequest& httpRequest() override { return request; }
187
188 operator bool() override { return active && request.isReady(); }
189
191 virtual void setTimeout(int ms) { clientTimeout = ms; }
192
195 void setPowerSave(bool ps) override { is_power_save = ps; }
196
198 void setAutoCreateLines(bool flag) {
199 httpRequest().reply().setAutoCreateLines(flag);
200 }
201
203 void setConnectionClose(bool close) override {
204 httpRequest().setConnection(close ? CON_CLOSE : CON_KEEP_ALIVE);
205 }
206
208 void clear() {
209 httpRequest().reply().clear();
211 read_buffer.resize(0);
212 read_pos = 0;
213 read_size = 0;
214 }
215
217 void addRequestHeader(const char* key, const char* value) override {
218 request.header().put(key, value);
219 }
220
221 const char* getReplyHeader(const char* key) override {
222 return request.reply().get(key);
223 }
224
226 void setOnConnectCallback(void (*callback)(
227 HttpRequest& request, Url& url, HttpRequestHeader& request_header)) {
228 request.setOnConnectCallback(callback);
229 }
230
231 void setWaitForData(bool flag) { wait_for_data = flag; }
232
233 int contentLength() override { return size; }
234
235 size_t totalRead() override { return total_read; }
237 bool waitForData (int timeout) override{
238 TRACED();
239 uint32_t end = millis() + timeout;
240 if (request.available() == 0) {
241 LOGI("Request written ... waiting for reply");
242 while (request.available() == 0) {
243 if (millis() > end) break;
244 // stop waiting if we got an error
245 if (request.reply().statusCode() >= 300) {
246 LOGE("Error code recieved ... stop waiting for reply");
247 break;
248 }
249 delay(500);
250 }
251 }
252 LOGD("available: %d", request.available());
253 return request.available() > 0;
254 }
255
256
257 const char* urlStr() override { return url_str.c_str(); }
258
260 void setCACert(const char* cert) override{
261 #ifdef USE_WIFI_CLIENT_SECURE
262 if (clientSecure!=nullptr) clientSecure->setCACert(cert);
263 #endif
264 }
265
266 protected:
267 HttpRequest request;
268 Str url_str;
269 Url url;
270 long size;
271 long total_read;
272 // buffered single byte read
273 Vector<uint8_t> read_buffer{0};
274 uint16_t read_buffer_size = DEFAULT_BUFFER_SIZE;
275 uint16_t read_pos;
276 uint16_t read_size;
277 bool active = false;
278 bool wait_for_data = true;
279 // optional
280 const char* network = nullptr;
281 const char* password = nullptr;
282 Client* client = nullptr; // client defined via setClient
283#ifdef USE_WIFI
284 WiFiClient* clientInsecure = nullptr; // wifi client for http
285#endif
286#ifdef USE_WIFI_CLIENT_SECURE
287 WiFiClientSecure* clientSecure = nullptr; // wifi client for https
288#endif
289 int clientTimeout = URL_CLIENT_TIMEOUT; // 60000;
290 unsigned long handshakeTimeout = URL_HANDSHAKE_TIMEOUT; // 120000
291 bool is_power_save = false;
292
293 bool preProcess(const char* urlStr, const char* acceptMime) {
294 TRACED();
295 url_str = urlStr;
296 url.setUrl(url_str.c_str());
297 int result = -1;
298
299 // close it - if we have an active connection
300 if (active) end();
301
302#ifdef USE_WIFI
303 // optional: login if necessary if no external client is defined
304 if (client == nullptr){
305 if (!login()){
306 LOGE("Not connected");
307 return false;
308 }
309 }
310#endif
311
312 // request.reply().setAutoCreateLines(false);
313 if (acceptMime != nullptr) {
314 request.setAcceptMime(acceptMime);
315 }
316
317 // setup client
318 Client& client = getClient(url.isSecure());
319 request.setClient(client);
320
321 // set timeout
322 client.setTimeout(clientTimeout / 1000);
323 request.setTimeout(clientTimeout);
324
325#if defined(ESP32) && defined(USE_WIFI_CLIENT_SECURE)
326 // There is a bug in IDF 4!
327 if (clientSecure != nullptr) {
328 clientSecure->setHandshakeTimeout(handshakeTimeout);
329 }
330
331 // Performance optimization for ESP32
332 if (!is_power_save) {
333 esp_wifi_set_ps(WIFI_PS_NONE);
334 }
335#endif
336
337 return true;
338 }
339
341 template <typename T>
342 int process(MethodID action, Url& url, const char* reqMime, T reqData,
343 int len = -1) {
344 TRACED();
345 // keep icy across redirect requests ?
346 const char* icy = request.header().get("Icy-MetaData");
347
348 int status_code = request.process(action, url, reqMime, reqData, len);
349 // redirect
350 while (request.reply().isRedirectStatus()) {
351 const char* redirect_url = request.reply().get(LOCATION);
352 if (redirect_url != nullptr) {
353 LOGW("Redirected to: %s", redirect_url);
354 url.setUrl(redirect_url);
355 Client* p_client = &getClient(url.isSecure());
356 p_client->stop();
357 request.setClient(*p_client);
358 if (icy) {
359 request.header().put("Icy-MetaData", icy);
360 }
361 status_code = request.process(action, url, reqMime, reqData, len);
362 } else {
363 LOGE("Location is null");
364 break;
365 }
366 }
367 return status_code;
368 }
369
371 Client& getClient(bool isSecure) {
372#ifdef USE_WIFI_CLIENT_SECURE
373 if (isSecure) {
374 if (clientSecure == nullptr) {
375 clientSecure = new WiFiClientSecure();
376 clientSecure->setInsecure();
377 }
378 LOGI("WiFiClientSecure");
379 return *clientSecure;
380 }
381#endif
382#ifdef USE_WIFI
383 if (clientInsecure == nullptr) {
384 clientInsecure = new WiFiClient();
385 LOGI("WiFiClient");
386 }
387 return *clientInsecure;
388#else
389 if (client == nullptr){
390 LOGE("Client not set");
391 stop();
392 }
393 return *client; // to avoid compiler warning
394#endif
395 }
396
397 inline void fillBuffer() {
398 if (isEOS()) {
399 // if we consumed all bytes we refill the buffer
400 read_size = readBytes(&read_buffer[0], read_buffer_size);
401 read_pos = 0;
402 }
403 }
404
405 inline bool isEOS() { return read_pos >= read_size; }
406
407 bool login() {
408#ifdef USE_WIFI
409 if (network != nullptr && password != nullptr &&
410 WiFi.status() != WL_CONNECTED) {
411 TRACEI();
412 WiFi.begin(network, password);
413 while (WiFi.status() != WL_CONNECTED) {
414 Serial.print(".");
415 delay(500);
416 }
417 Serial.println();
418 delay(10);
419 return WiFi.status() == WL_CONNECTED;
420 }
421 return WiFi.status() == WL_CONNECTED;
422#else
423 return false;
424#endif
425 }
426};
427
428using ICYStream = ICYStreamT<URLStream>;
429
430#if defined(USE_CONCURRENCY)
431using URLStreamBuffered = URLStreamBufferedT<URLStream>;
432using ICYStreamBuffered = URLStreamBufferedT<ICYStream>;
433#endif
434
435} // namespace audio_tools
436
437#endif
Abstract Base class for all URLStream implementations.
Definition AbstractURLStream.h:17
Definition NoArduino.h:169
HttpHeader & clear()
clears the data
Definition HttpHeader.h:74
bool isRedirectStatus()
returns true if the status code is >=300 and < 400
Definition HttpHeader.h:288
void setAutoCreateLines(bool is_auto_line)
automatically create new lines
Definition HttpHeader.h:280
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(size_t 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
size_t totalRead() override
Total amout of data that was consumed so far.
Definition URLStream.h:235
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:191
bool waitForData(int timeout) override
waits for some data - returns false if the request has failed
Definition URLStream.h:237
void setConnectionClose(bool close) override
Sets if the connection should be close automatically.
Definition URLStream.h:203
void setReadBufferSize(int readBufferSize)
Defines the buffer that is used by individual read() or peek() calls.
Definition URLStream.h:82
void setPowerSave(bool ps) override
Definition URLStream.h:195
Client & getClient(bool isSecure)
Determines the client.
Definition URLStream.h:371
int process(MethodID action, Url &url, const char *reqMime, T reqData, int len=-1)
Process the Http request and handle redirects.
Definition URLStream.h:342
void addRequestHeader(const char *key, const char *value) override
Adds/Updates a request header.
Definition URLStream.h:217
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:110
const char * urlStr() override
Provides the url as string.
Definition URLStream.h:257
void setClient(Client &clientPar) override
(Re-)defines the client
Definition URLStream.h:73
void setCACert(const char *cert) override
Define the Root PEM Certificate for SSL.
Definition URLStream.h:260
void setOnConnectCallback(void(*callback)(HttpRequest &request, Url &url, HttpRequestHeader &request_header))
Callback which allows you to add additional paramters dynamically.
Definition URLStream.h:226
void clear()
Releases the memory from the request and reply.
Definition URLStream.h:208
int contentLength() override
Provides the reported data size from the http reply.
Definition URLStream.h:233
void setAutoCreateLines(bool flag)
If set to true, new undefined reply parameters will be stored.
Definition URLStream.h:198
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:186
const char * getReplyHeader(const char *key) override
Provides reply header information.
Definition URLStream.h:221
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:14
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
uint32_t millis()
Returns the milliseconds since the start.
Definition Time.h:12