arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
HttpHeader.h
1#pragma once
2
3#include "AudioTools/CoreAudio/AudioBasic/Collections.h"
4#include "AudioTools/CoreAudio/AudioBasic/Str.h"
5#include "AudioConfig.h"
6#include "AudioClient.h" // for Client
7#include "HttpLineReader.h"
8#include "HttpTypes.h"
9#include "Url.h"
10
11namespace audio_tools {
12
13// Class Configuration
14
15// Define relevant header content
16static const char* CONTENT_TYPE = "Content-Type";
17static const char* CONTENT_LENGTH = "Content-Length";
18static const char* CONNECTION = "Connection";
19static const char* CON_CLOSE = "close";
20static const char* CON_KEEP_ALIVE = "keep-alive";
21static const char* TRANSFER_ENCODING = "Transfer-Encoding";
22static const char* CHUNKED = "chunked";
23static const char* ACCEPT = "Accept";
24static const char* ACCEPT_ALL = "*/*";
25static const char* SUCCESS = "Success";
26static const char* USER_AGENT = "User-Agent";
27static const char* DEFAULT_AGENT =
28 "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)";
29static const char* HOST_C = "Host";
30static const char* ACCEPT_ENCODING = "Accept-Encoding";
31static const char* IDENTITY = "identity";
32static const char* LOCATION = "Location";
33
34// Http methods
35static const char* methods[] = {"?", "GET", "HEAD", "POST",
36 "PUT", "DELETE", "TRACE", "OPTIONS",
37 "CONNECT", "PATCH", nullptr};
38
44 Str key;
45 Str value;
46 bool active = true;
47 HttpHeaderLine(const char* k) { key = k; }
48};
49
60 public:
61 HttpHeader() {
62 LOGD("HttpHeader");
63 // set default values
64 protocol_str = "HTTP/1.1";
65 url_path = "/";
66 status_msg = "";
67 }
68 ~HttpHeader() {
69 LOGD("~HttpHeader");
70 clear();
71 }
72
75 is_written = false;
76 is_chunked = false;
77 url_path = "/";
78 // delete HttpHeaderLine objects
79 for (auto& ptr : lines){
80 delete ptr;
81 }
82 lines.clear();
83 return *this;
84 }
85
86 HttpHeader& put(const char* key, const char* value) {
87 if (value != nullptr && strlen(value) > 0) {
88 LOGD("HttpHeader::put %s %s", key, value);
89 HttpHeaderLine* hl = headerLine(key);
90 if (hl == nullptr) {
91 if (create_new_lines){
92 LOGE("HttpHeader::put - did not add HttpHeaderLine for %s", key);
93 }
94 return *this;
95 }
96
97 // log entry
98 LOGD("HttpHeader::put -> '%s' : '%s'", key, value);
99
100 hl->value = value;
101 hl->active = true;
102
103 // determine if chunked
104 if (StrView(key).equalsIgnoreCase(TRANSFER_ENCODING) && StrView(value) == CHUNKED) {
105 LOGI("- is_chunked!!!");
106 this->is_chunked = true;
107 }
108 // log content type
109 if (StrView(key).equalsIgnoreCase(CONTENT_TYPE)) {
110 LOGI("%s: %s", CONTENT_TYPE, value);
111 }
112 } else {
113 LOGD("HttpHeader::put - value ignored because it is null for %s", key);
114 }
115 return *this;
116 }
117
119 HttpHeader& put(const char* key, int value) {
120 LOGD("HttpHeader::put %s %d", key, value);
121 HttpHeaderLine* hl = headerLine(key);
122
123 if (value > 1000) {
124 LOGW("value is > 1000");
125 }
126
127 // add value
128 hl->value = value;
129 hl->active = true;
130 LOGI("%s %s", key, hl->value.c_str());
131 return *this;
132 }
133
135 HttpHeader& put(const char* line) {
136 LOGD("HttpHeader::put -> %s", (const char*)line);
138 int pos = keyStr.indexOf(":");
139 char* key = (char*)line;
140 key[pos] = 0;
141
142 // usually there is a leading space - but unfurtunately not always
143 const char* value = line + pos + 1;
144 if (value[0] == ' ') {
145 value = line + pos + 2;
146 }
147 return put((const char*)key, value);
148 }
149
150 // determines a header value with the key
151 const char* get(const char* key) {
152 for (auto& line_ptr : lines) {
153 StrView trimmed{line_ptr->key.c_str()};
154 trimmed.trim();
155 line_ptr->key = trimmed.c_str();
156 //line->key.rtrim();
157 if (StrView(line_ptr->key.c_str()).equalsIgnoreCase(key)) {
158 const char* result = line_ptr->value.c_str();
159 return line_ptr->active ? result : nullptr;
160 }
161 }
162 return nullptr;
163 }
164
165 // reads a single header line
166 int readLine(Client& in, char* str, int len) {
167 int result = reader.readlnInternal(in, (uint8_t*)str, len, false);
168 LOGD("HttpHeader::readLine -> %s", str);
169 return result;
170 }
171
172 // writes a lingle header line
173 void writeHeaderLine(Client& out, HttpHeaderLine& header) {
174 LOGD("HttpHeader::writeHeaderLine: %s", header.key.c_str());
175 if (!header.active) {
176 LOGD("HttpHeader::writeHeaderLine - not active");
177 return;
178 }
179 if (header.value.c_str() == nullptr) {
180 LOGD("HttpHeader::writeHeaderLine - ignored because value is null");
181 return;
182 }
183
184 char* msg = tempBuffer();
185 StrView msg_str(msg, HTTP_MAX_LEN);
186 msg_str = header.key.c_str();
187 msg_str += ": ";
188 msg_str += header.value.c_str();
189 msg_str += CRLF;
190 out.print(msg_str.c_str());
191
192 // remove crlf from log
193
194 int len = strlen(msg);
195 msg[len - 2] = 0;
196 LOGI(" -> %s ", msg);
197
198 // mark as processed
199 // header->active = false;
200 }
201
202 const char* urlPath() { return url_path.c_str(); }
203
204 MethodID method() { return method_id; }
205
206 int statusCode() { return status_code; }
207
208 const char* statusMessage() { return status_msg.c_str(); }
209
210 bool isChunked() {
211 // the value is automatically set from the reply
212 return is_chunked;
213 }
214
216 bool read(Client& in) {
217 LOGD("HttpHeader::read");
218 // remove all existing value
219 clear();
220
221 char* line = tempBuffer();
222 if (in.connected()) {
223 if (in.available() == 0) {
224 int count = 0;
225 uint32_t timeout = millis() + timeout_ms;
226 while (in.available() == 0) {
227 delay(50);
228 count++;
229 if (count == 2) {
230 LOGI("Waiting for data...");
231 }
232 // If we dont get an answer, we abort
233 if (millis() > timeout) {
234 LOGE("Request timed out after %d ms", (int)timeout_ms);
235 status_code = 401;
236 return false;
237 }
238 }
239 LOGI("Data available: %d", in.available());
240 }
241
242 readLine(in, line, HTTP_MAX_LEN);
243 parse1stLine(line);
244 while (true) {
245 int len = readLine(in, line, HTTP_MAX_LEN);
246 if (len == 0 && in.available() == 0) break;
247 if (isValidStatus() || isRedirectStatus()) {
249 lineStr.ltrim();
250 if (lineStr.isEmpty()) {
251 break;
252 }
253 put(line);
254 }
255 }
256 }
257 return true;
258 }
259
261 void write(Client& out) {
262 LOGI("HttpHeader::write");
263 write1stLine(out);
264 for (auto& line_ptr : lines) {
265 writeHeaderLine(out, *line_ptr);
266 }
267 // print empty line
268 crlf(out);
269 out.flush();
270 is_written = true;
271 }
272
273 void setProcessed() {
274 for (auto& line :lines) {
275 line->active = false;
276 }
277 }
278
281 create_new_lines = is_auto_line;
282 }
283
285 bool isValidStatus() { return status_code >= 200 && status_code < 300; }
286
288 bool isRedirectStatus() { return status_code >= 300 && status_code < 400; }
289
291 void end() { temp_buffer.resize(0); }
292
294 void setTimeout(int timeoutMs) { timeout_ms = timeoutMs; }
295
297 const char* protocol() { return protocol_str.c_str(); }
298
300 void setProtocol(const char* protocal) { protocol_str = protocal; }
301
303 void resize(int bufferSize){
304 temp_buffer.resize(bufferSize);
305 }
306
309 return lines;
310 }
311
312 protected:
313 int status_code = UNDEFINED;
314 bool is_written = false;
315 bool is_chunked = false;
316 bool create_new_lines = true;
317 MethodID method_id;
318 // we store the values on the heap. this is acceptable because we just have
319 // one instance for the requests and one for the replys: which needs about
320 // 2*100 bytes
321 Str protocol_str{10};
322 Str url_path{70};
323 Str status_msg{20};
324 List<HttpHeaderLine*> lines;
325 HttpLineReader reader;
326 const char* CRLF = "\r\n";
327 int timeout_ms = URL_CLIENT_TIMEOUT;
328 Vector<char> temp_buffer{HTTP_MAX_LEN};
329
330 char* tempBuffer() {
331 temp_buffer.clear();
332 return temp_buffer.data();
333 }
334
335 // the headers need to delimited with CR LF
336 void crlf(Client& out) {
337 out.print(CRLF);
338 LOGI(" -> %s ", "<CR LF>");
339 }
340
341 // gets or creates a header line by key
342 HttpHeaderLine* headerLine(const char* key) {
343 if (key != nullptr) {
344 for (auto& line_ptr : lines) {
345 if (line_ptr->key.c_str() != nullptr) {
346 if (line_ptr->key.equalsIgnoreCase(key)) {
347 line_ptr->active = true;
348 return line_ptr;
349 }
350 }
351 }
352 if (create_new_lines || StrView(key).equalsIgnoreCase(CONTENT_LENGTH) ||
353 StrView(key).equalsIgnoreCase(CONTENT_TYPE)) {
354 HttpHeaderLine *new_line = new HttpHeaderLine(key);
355 lines.push_back(new_line);
356 return new_line;
357 }
358 } else {
359 LOGI("HttpHeader::headerLine %s", "The key must not be null");
360 }
361 return nullptr;
362 }
363
364 MethodID getMethod(const char* line) {
365 // set action
366 for (int j = 0; methods[j] != nullptr; j++) {
367 const char* action = methods[j];
368 int len = strlen(action);
369 if (strncmp(action, line, len) == 0) {
370 return (MethodID)j;
371 }
372 }
373 return (MethodID)0;
374 }
375
376 virtual void write1stLine(Client& out) = 0;
377 virtual void parse1stLine(const char* line) = 0;
378};
379
386 public:
387 // Defines the action id, url path and http version for an request
388 HttpHeader& setValues(MethodID id, const char* urlPath,
389 const char* protocol = nullptr) {
390 this->method_id = id;
391 this->url_path = urlPath;
392
393 LOGD("HttpRequestHeader::setValues - path: %s", this->url_path.c_str());
394 if (protocol != nullptr) {
395 this->protocol_str = protocol;
396 }
397 return *this;
398 }
399
400 // action path protocol
401 void write1stLine(Client& out) {
402 LOGD("HttpRequestHeader::write1stLine");
403 char* msg = tempBuffer();
404 StrView msg_str(msg, HTTP_MAX_LEN);
405
406 const char* method_str = methods[this->method_id];
408 msg_str += " ";
409 msg_str += this->url_path.c_str();
410 msg_str += " ";
411 msg_str += this->protocol_str.c_str();
412 msg_str += CRLF;
413 out.print(msg);
414
415 int len = strlen(msg);
416 msg[len - 2] = 0;
417 LOGI("-> %s", msg);
418 }
419
420 // parses the requestline
421 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
422 void parse1stLine(const char* line) {
423 LOGD("HttpRequestHeader::parse1stLine %s", line);
425 int space1 = line_str.indexOf(" ");
426 int space2 = line_str.indexOf(" ", space1 + 1);
427
428 this->method_id = getMethod(line);
429 this->protocol_str.substring(line_str, space2 + 1, line_str.length());
430 this->url_path.substring(line_str, space1 + 1, space2);
431 this->url_path.trim();
432
433 LOGD("->method %s", methods[this->method_id]);
434 LOGD("->protocol %s", protocol_str.c_str());
435 LOGD("->url_path %s", url_path.c_str());
436 }
437};
438
445 public:
446 // defines the values for the rely
447 void setValues(int statusCode, const char* msg = "",
448 const char* protocol = nullptr) {
449 LOGI("HttpReplyHeader::setValues");
450 status_msg = msg;
451 status_code = statusCode;
452 if (protocol != nullptr) {
453 this->protocol_str = protocol;
454 }
455 }
456
457 // reads the final chunked reply headers
458 void readExt(Client& in) {
459 LOGI("HttpReplyHeader::readExt");
460 char* line = tempBuffer();
461 readLine(in, line, HTTP_MAX_LEN);
462 while (strlen(line) != 0) {
463 put(line);
464 readLine(in, line, HTTP_MAX_LEN);
465 }
466 }
467
468 // HTTP-Version SP Status-Code SP Reason-Phrase CRLF
469 void write1stLine(Client& out) {
470 LOGI("HttpReplyHeader::write1stLine");
471 char* msg = tempBuffer();
472 StrView msg_str(msg, HTTP_MAX_LEN);
473 msg_str = this->protocol_str.c_str();
474 msg_str += " ";
475 msg_str += this->status_code;
476 msg_str += " ";
477 msg_str += this->status_msg.c_str();
478 LOGI("-> %s", msg);
479 out.print(msg);
480 crlf(out);
481 }
482
483 // HTTP-Version SP Status-Code SP Reason-Phrase CRLF
484 // we just update the pointers to point to the correct position in the
485 // http_status_line
486 void parse1stLine(const char* line) {
487 LOGD("HttpReplyHeader::parse1stLine: %s", line);
489 int space1 = line_str.indexOf(' ', 0);
490 int space2 = line_str.indexOf(' ', space1 + 1);
491
492 // save http version
493 protocol_str.substring(line_str, 0, space1);
494
495 // find response status code after the first space
496 char status_c[6];
497 StrView status(status_c, 6);
498 status.substring(line_str, space1 + 1, space2);
499 status_code = atoi(status_c);
500
501 // get reason-phrase after last SP
502 status_msg.substring(line_str, space2 + 1, line_str.length());
503 }
504};
505
506} // namespace audio_tools
Definition NoArduino.h:167
In a http request and reply we need to process header information. With this API we can define and qu...
Definition HttpHeader.h:59
List< HttpHeaderLine * > & getHeaderLines()
Provides the http parameter lines.
Definition HttpHeader.h:308
void setTimeout(int timeoutMs)
Set the timout.
Definition HttpHeader.h:294
HttpHeader & clear()
clears the data
Definition HttpHeader.h:74
void setProtocol(const char *protocal)
Defines the protocol: e.g. HTTP/1.1.
Definition HttpHeader.h:300
void write(Client &out)
writes the full header to the indicated HttpStreamedMultiOutput stream
Definition HttpHeader.h:261
void resize(int bufferSize)
Resizes the internal read buffer.
Definition HttpHeader.h:303
bool isRedirectStatus()
returns true if the status code is >=300 and < 400
Definition HttpHeader.h:288
void end()
release static temp buffer
Definition HttpHeader.h:291
void setAutoCreateLines(bool is_auto_line)
automatically create new lines
Definition HttpHeader.h:280
HttpHeader & put(const char *key, int value)
adds a new line to the header - e.g. for content size
Definition HttpHeader.h:119
bool read(Client &in)
reads the full header from the request (stream)
Definition HttpHeader.h:216
bool isValidStatus()
returns true if status code >=200 and < 300
Definition HttpHeader.h:285
const char * protocol()
Provide the protocol.
Definition HttpHeader.h:297
HttpHeader & put(const char *line)
adds a received new line to the header
Definition HttpHeader.h:135
Reading and Writing of Http Replys.
Definition HttpHeader.h:444
Reading and writing of Http Requests.
Definition HttpHeader.h:385
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
A simple wrapper to provide string functions on existing allocated char*. If the underlying char* is ...
Definition StrView.h:28
virtual void trim()
remove leading and traling spaces
Definition StrView.h:504
virtual void substring(StrView &from, int start, int end)
copies a substring into the current string
Definition StrView.h:477
virtual const char * c_str()
provides the string value as const char*
Definition StrView.h:379
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
uint32_t millis()
Returns the milliseconds since the start.
Definition Time.h:12
A individual key - value header line.
Definition HttpHeader.h:43