arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
HttpRequest.h
1#pragma once
2
3#include "AudioConfig.h"
4#include "HttpChunkReader.h"
5#include "HttpHeader.h"
6#include "HttpTypes.h"
7#include "Url.h"
8#include "AudioLogger.h"
9
10#define CHUNK_SIZE 1024
11
12namespace audio_tools {
13
25class HttpRequest : public BaseStream {
26 public:
27// friend class URLStream;
28
29 HttpRequest() = default;
30
31 ~HttpRequest() { end(); }
32
33 HttpRequest(Client &client) { setClient(client); }
34
35 void setClient(Client &client) {
36 this->client_ptr = &client;
37 this->client_ptr->setTimeout(clientTimeout);
38 }
39
40 // the requests usually need a host. This needs to be set if we did not
41 // provide a URL
42 void setHost(const char *host) {
43 LOGI("setHost %s", host);
44 this->host_name = host;
45 }
46
47 operator bool() { return client_ptr != nullptr ? (bool)*client_ptr : false; }
48
49 virtual bool connected() {
50 return client_ptr == nullptr ? false : client_ptr->connected();
51 }
52
53 virtual int available() override {
54 if (reply_header.isChunked()) {
55 return chunk_reader.available();
56 }
57 return client_ptr != nullptr ? client_ptr->available() : 0;
58 }
59
61 void end() override {
62 if (connected()) {
63 // write final 0 chunk if necessary
64 if (is_chunked_output_active) {
65 client_ptr->println(0, HEX);
66 client_ptr->println();
67 client_ptr->flush();
68 is_chunked_output_active = false;
69 }
70 LOGI("stop");
71 client_ptr->stop();
72 }
73 }
74
75 virtual void stop() {
76 end();
77 }
78
80 virtual int post(Url &url, const char *mime, const char *data, int len = -1) {
81 LOGI("post %s", url.url());
82 return process(POST, url, mime, data, len);
83 }
84
86 virtual int post(Url &url, const char *mime, Stream &data, int len = -1) {
87 LOGI("post %s", url.url());
88 return process(POST, url, mime, data, len);
89 }
90
92 virtual int put(Url &url, const char *mime, const char *data, int len = -1) {
93 LOGI("put %s", url.url());
94 return process(PUT, url, mime, data, len);
95 }
96
98 virtual int put(Url &url, const char *mime, Stream &data, int len = -1) {
99 LOGI("put %s", url.url());
100 return process(PUT, url, mime, data, len);
101 }
102
104 virtual int del(Url &url, const char *mime = nullptr,
105 const char *data = nullptr, int len = -1) {
106 LOGI("del %s", url.url());
107 return process(DELETE, url, mime, data, len);
108 }
109
111 virtual int get(Url &url, const char *acceptMime = nullptr,
112 const char *data = nullptr, int len = -1) {
113 LOGI("get %s", url.url());
114 this->accept = acceptMime;
115 return process(GET, url, nullptr, data, len);
116 }
117
119 virtual int head(Url &url, const char *acceptMime = nullptr,
120 const char *data = nullptr, int len = -1) {
121 LOGI("head %s", url.url());
122 this->accept = acceptMime;
123 return process(HEAD, url, nullptr, data, len);
124 }
125
126 // reads the reply data
127 virtual int read(uint8_t *str, int len) {
128 TRACED();
129 if (reply_header.isChunked()) {
130 return chunk_reader.read(*client_ptr, str, len);
131 } else {
132 return client_ptr->read(str, len);
133 }
134 }
135
136 size_t readBytes(uint8_t *str, size_t len) override {
137 return read(str, len);
138 }
139
140 size_t readBytesUntil(char terminator, char *buffer, size_t length) {
141 TRACED();
142 return client_ptr->readBytesUntil(terminator, buffer, length);
143 }
144
145 // read the reply data up to the next new line. For Chunked data we provide
146 // the full chunk!
147 virtual int readln(uint8_t *str, int len, bool incl_nl = true) {
148 TRACED();
149 if (reply_header.isChunked()) {
150 return chunk_reader.readln(*client_ptr, str, len);
151 } else {
152 return chunk_reader.readlnInternal(*client_ptr, str, len, incl_nl);
153 }
154 }
155
156 // provides the head information of the reply
157 virtual HttpReplyHeader &reply() { return reply_header; }
158
160 virtual HttpRequestHeader &header() { return request_header; }
161
163 virtual void setAgent(const char *agent) { this->agent = agent; }
164
165 virtual void setConnection(const char *connection) {
166 this->connection = connection;
167 }
168
169 virtual void setAcceptsEncoding(const char *enc) {
170 this->accept_encoding = enc;
171 }
172
173 virtual void setAcceptMime(const char *mime) { this->accept = mime; }
174
175 size_t contentLength() {
176 const char *len_str = reply().get(CONTENT_LENGTH);
177 int len = 0;
178 if (len_str != nullptr) {
179 len = atoi(len_str);
180 } else {
181 LOGI("no CONTENT_LENGTH found in reply");
182 }
183 return len;
184 }
185
188 bool isReady() { return is_ready; }
189
191 void addRequestHeader(const char *key, const char *value) {
192 request_header.put(key, value);
193 }
194 const char* getReplyHeader(const char *key) {
195 return reply_header.get(key);
196 }
197
198 Client &client() { return *client_ptr; }
199
200 // process http request and reads the reply_header from the server
201 virtual int process(MethodID action, Url &url, const char *mime,
202 const char *data, int lenData = -1) {
203 int len = lenData;
204 if (data != nullptr && len <= 0) {
205 len = strlen(data);
206 }
207 processBegin(action, url, mime, len);
208 // posting data parameter
209 if (len > 0 && data != nullptr) {
210 LOGI("Writing data: %d bytes", len);
211 client_ptr->write((const uint8_t *)data, len);
212 LOGD("%s", data);
213 }
214 return processEnd();
215 }
216
217 // process http request and reads the reply_header from the server
218 virtual int process(MethodID action, Url &url, const char *mime,
219 Stream &stream, int len = -1) {
220 if (!processBegin(action, url, mime, len))
221 return -1;
222 processWrite(stream);
223 return processEnd();
224 }
225
227 virtual bool processBegin(MethodID action, Url &url, const char *mime,
228 int lenData = -1) {
229 TRACED();
230 int len = lenData;
231 is_ready = false;
232 if (client_ptr == nullptr) {
233 LOGE("The client has not been defined");
234 return false;
235 }
236 if (http_connect_callback) {
237 http_connect_callback(*this, url, request_header);
238 }
239 if (!this->connected()) {
240 LOGI("process connecting to host %s port %d", url.host(), url.port());
241 int is_connected = connect(url.host(), url.port(), clientTimeout);
242 if (!is_connected) {
243 LOGE("Connect failed");
244 return false;
245 }
246 } else {
247 LOGI("process is already connected");
248 }
249
250#if defined(ESP32) && defined(ARDUINO)
251 LOGI("Free heap: %u", (unsigned)ESP.getFreeHeap());
252#endif
253
254 reply_header.setProcessed();
255
256 host_name = url.host();
257 request_header.setValues(action, url.path());
258 if (lenData > 0) {
259 request_header.put(CONTENT_LENGTH, lenData);
260 }
261 request_header.put(HOST_C, host_name);
262 request_header.put(CONNECTION, connection);
263 request_header.put(USER_AGENT, agent);
264 request_header.put(ACCEPT_ENCODING, accept_encoding);
265 request_header.put(ACCEPT, accept);
266 request_header.put(CONTENT_TYPE, mime);
267 request_header.write(*client_ptr);
268
269 return true;
270 }
271
273 virtual void processWrite(Stream &stream) {
274 uint8_t buffer[CHUNK_SIZE];
275 int total = 0;
276 int total_written = 0;
277 while (*client_ptr && stream.available() > 0) {
278 int result_len = stream.readBytes(buffer, CHUNK_SIZE);
279 total += result_len;
280 int written = write(buffer, result_len);
281 total_written += written;
282 LOGI("--> Bytes read %d vs written %d", result_len, written);
283 delay(1);
284 }
285 client_ptr->flush();
286 LOGI("Total bytes read %d vs written %d", total, total_written);
287 }
288
291 size_t write(const uint8_t *data, size_t len) override {
292 TRACED();
293 size_t result = 0;
294 if (isChunked()) {
295 if (len > 0) {
296 is_chunked_output_active = true;
297 client_ptr->println(len, HEX);
298 result = client_ptr->write(data, len);
299 client_ptr->println();
300 }
301 } else {
302 result = client_ptr->write(data, len);
303 }
304 return result;
305 }
306
308 virtual int processEnd() {
309 TRACED();
310 // if sending is chunked we terminate with an empty chunk
311 if (isChunked()) {
312 if (is_chunked_output_active) client_ptr->println(0, HEX);
313 client_ptr->println();
314 is_chunked_output_active = false;
315 }
316 LOGI("Request written ... waiting for reply");
317 // Commented out because this breaks the RP2040 W
318 // client_ptr->flush();
319 reply_header.read(*client_ptr);
320
321 // if we use chunked tranfer we need to read the first chunked length
322 if (reply_header.isChunked()) {
323 chunk_reader.open(*client_ptr);
324 };
325
326 // wait for data
327 is_ready = true;
328 return reply_header.statusCode();
329 }
330
332 void setOnConnectCallback(void (*callback)(
333 HttpRequest &request, Url &url, HttpRequestHeader &request_header)) {
334 http_connect_callback = callback;
335 }
336
338 void setTimeout(int timeoutMs) { clientTimeout = timeoutMs; }
339
341 bool isChunked() { return request_header.isChunked(); }
342
343 protected:
344 Client *client_ptr = nullptr;
345 Url url;
346 HttpRequestHeader request_header;
347 HttpReplyHeader reply_header;
348 HttpChunkReader chunk_reader = HttpChunkReader(reply_header);
349 const char *agent = nullptr;
350 const char *host_name = nullptr;
351 const char *connection = CON_KEEP_ALIVE;
352 const char *accept = ACCEPT_ALL;
353 const char *accept_encoding = IDENTITY;
354 bool is_ready = false;
355 int32_t clientTimeout = URL_CLIENT_TIMEOUT; // 60000;
356 void (*http_connect_callback)(HttpRequest &request, Url &url,
357 HttpRequestHeader &request_header) = nullptr;
358 bool is_chunked_output_active = false;
359
360 // opens a connection to the indicated host
361 virtual int connect(const char *ip, uint16_t port, int32_t timeout) {
362 TRACED();
363 client_ptr->setTimeout(timeout / 1000); // client timeout is in seconds!
364 request_header.setTimeout(timeout);
365 reply_header.setTimeout(timeout);
366 int is_connected = this->client_ptr->connect(ip, port);
367 LOGI("is connected %s with timeout %d", is_connected ? "true" : "false", (int)timeout);
368 return is_connected;
369 }
370};
371
372} // namespace audio_tools
373
Base class for all Streams. It relies on write(const uint8_t *buffer, size_t size) and readBytes(uint...
Definition BaseStream.h:36
Definition NoArduino.h:167
Http might reply with chunks. So we need to dechunk the data. see https://en.wikipedia....
Definition HttpChunkReader.h:14
void setTimeout(int timeoutMs)
Set the timout.
Definition HttpHeader.h:294
void write(Client &out)
writes the full header to the indicated HttpStreamedMultiOutput stream
Definition HttpHeader.h:261
bool read(Client &in)
reads the full header from the request (stream)
Definition HttpHeader.h:216
Reading and Writing of Http Replys.
Definition HttpHeader.h:444
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
bool isChunked()
we are sending the data chunked
Definition HttpRequest.h:341
virtual int post(Url &url, const char *mime, Stream &data, int len=-1)
http post
Definition HttpRequest.h:86
void addRequestHeader(const char *key, const char *value)
Adds/Updates a request header.
Definition HttpRequest.h:191
virtual int get(Url &url, const char *acceptMime=nullptr, const char *data=nullptr, int len=-1)
http get
Definition HttpRequest.h:111
virtual int processEnd()
Ends the http request processing and returns the status code.
Definition HttpRequest.h:308
virtual int put(Url &url, const char *mime, const char *data, int len=-1)
http put
Definition HttpRequest.h:92
virtual void processWrite(Stream &stream)
Writes (Posts) the data of the indicated stream after calling processBegin.
Definition HttpRequest.h:273
virtual void setAgent(const char *agent)
Defines the agent.
Definition HttpRequest.h:163
void end() override
same as end()
Definition HttpRequest.h:61
size_t write(const uint8_t *data, size_t len) override
Definition HttpRequest.h:291
virtual bool processBegin(MethodID action, Url &url, const char *mime, int lenData=-1)
starts http request processing
Definition HttpRequest.h:227
virtual int put(Url &url, const char *mime, Stream &data, int len=-1)
http put
Definition HttpRequest.h:98
virtual int head(Url &url, const char *acceptMime=nullptr, const char *data=nullptr, int len=-1)
http head
Definition HttpRequest.h:119
virtual int post(Url &url, const char *mime, const char *data, int len=-1)
http post
Definition HttpRequest.h:80
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
virtual int del(Url &url, const char *mime=nullptr, const char *data=nullptr, int len=-1)
http del
Definition HttpRequest.h:104
Definition NoArduino.h:142
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
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioConfig.h:885