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