Subversion Repositories ESP8266_P1_Meter

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 raymond 1
// SPDX-License-Identifier: LGPL-3.0-or-later
2
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
3
 
4
#include "ESPAsyncWebServer.h"
5
#include "WebResponseImpl.h"
6
#include "AsyncWebServerLogging.h"
7
 
8
#include <algorithm>
9
#include <memory>
10
#include <utility>
11
 
12
#ifndef CONFIG_LWIP_TCP_WND_DEFAULT
13
#ifdef TCP_WND  // ESP8266
14
#define CONFIG_LWIP_TCP_WND_DEFAULT TCP_WND
15
#else
16
// as it is defined for esp32's LWIP
17
#define CONFIG_LWIP_TCP_WND_DEFAULT 5760
18
#endif
19
#endif
20
 
21
using namespace asyncsrv;
22
 
23
/*
24
 * Abstract Response
25
 *
26
 */
27
 
28
STR_RETURN_TYPE AsyncWebServerResponse::responseCodeToString(int code) {
29
  switch (code) {
30
    case 100: return STR(T_HTTP_CODE_100);
31
    case 101: return STR(T_HTTP_CODE_101);
32
    case 200: return STR(T_HTTP_CODE_200);
33
    case 201: return STR(T_HTTP_CODE_201);
34
    case 202: return STR(T_HTTP_CODE_202);
35
    case 203: return STR(T_HTTP_CODE_203);
36
    case 204: return STR(T_HTTP_CODE_204);
37
    case 205: return STR(T_HTTP_CODE_205);
38
    case 206: return STR(T_HTTP_CODE_206);
39
    case 300: return STR(T_HTTP_CODE_300);
40
    case 301: return STR(T_HTTP_CODE_301);
41
    case 302: return STR(T_HTTP_CODE_302);
42
    case 303: return STR(T_HTTP_CODE_303);
43
    case 304: return STR(T_HTTP_CODE_304);
44
    case 305: return STR(T_HTTP_CODE_305);
45
    case 307: return STR(T_HTTP_CODE_307);
46
    case 400: return STR(T_HTTP_CODE_400);
47
    case 401: return STR(T_HTTP_CODE_401);
48
    case 402: return STR(T_HTTP_CODE_402);
49
    case 403: return STR(T_HTTP_CODE_403);
50
    case 404: return STR(T_HTTP_CODE_404);
51
    case 405: return STR(T_HTTP_CODE_405);
52
    case 406: return STR(T_HTTP_CODE_406);
53
    case 407: return STR(T_HTTP_CODE_407);
54
    case 408: return STR(T_HTTP_CODE_408);
55
    case 409: return STR(T_HTTP_CODE_409);
56
    case 410: return STR(T_HTTP_CODE_410);
57
    case 411: return STR(T_HTTP_CODE_411);
58
    case 412: return STR(T_HTTP_CODE_412);
59
    case 413: return STR(T_HTTP_CODE_413);
60
    case 414: return STR(T_HTTP_CODE_414);
61
    case 415: return STR(T_HTTP_CODE_415);
62
    case 416: return STR(T_HTTP_CODE_416);
63
    case 417: return STR(T_HTTP_CODE_417);
64
    case 429: return STR(T_HTTP_CODE_429);
65
    case 500: return STR(T_HTTP_CODE_500);
66
    case 501: return STR(T_HTTP_CODE_501);
67
    case 502: return STR(T_HTTP_CODE_502);
68
    case 503: return STR(T_HTTP_CODE_503);
69
    case 504: return STR(T_HTTP_CODE_504);
70
    case 505: return STR(T_HTTP_CODE_505);
71
    case 507: return STR(T_HTTP_CODE_507);
72
    default:  return STR(T_HTTP_CODE_ANY);
73
  }
74
}
75
 
76
AsyncWebServerResponse::AsyncWebServerResponse()
77
  : _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0),
78
    _state(RESPONSE_SETUP) {
79
  for (const auto &header : DefaultHeaders::Instance()) {
80
    _headers.emplace_back(header);
81
  }
82
}
83
 
84
void AsyncWebServerResponse::setCode(int code) {
85
  if (_state == RESPONSE_SETUP) {
86
    _code = code;
87
  }
88
}
89
 
90
void AsyncWebServerResponse::setContentLength(size_t len) {
91
  if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) {
92
    _contentLength = len;
93
  }
94
}
95
 
96
void AsyncWebServerResponse::setContentType(const char *type) {
97
  if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) {
98
    _contentType = type;
99
  }
100
}
101
 
102
bool AsyncWebServerResponse::removeHeader(const char *name) {
103
  bool h_erased = false;
104
  for (auto i = _headers.begin(); i != _headers.end();) {
105
    if (i->name().equalsIgnoreCase(name)) {
106
      _headers.erase(i);
107
      h_erased = true;
108
    } else {
109
      ++i;
110
    }
111
  }
112
  return h_erased;
113
}
114
 
115
bool AsyncWebServerResponse::removeHeader(const char *name, const char *value) {
116
  for (auto i = _headers.begin(); i != _headers.end(); ++i) {
117
    if (i->name().equalsIgnoreCase(name) && i->value().equalsIgnoreCase(value)) {
118
      _headers.erase(i);
119
      return true;
120
    }
121
  }
122
  return false;
123
}
124
 
125
const AsyncWebHeader *AsyncWebServerResponse::getHeader(const char *name) const {
126
  auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader &header) {
127
    return header.name().equalsIgnoreCase(name);
128
  });
129
  return (iter == std::end(_headers)) ? nullptr : &(*iter);
130
}
131
 
132
bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
133
  for (uint8_t i = 0; i < T_only_once_headers_len; i++) {
134
    if (name.equalsIgnoreCase(T_only_once_headers[i])) {
135
      return true;
136
    }
137
  }
138
  return false;
139
}
140
 
141
bool AsyncWebServerResponse::addHeader(AsyncWebHeader &&header, bool replaceExisting) {
142
  if (!header) {
143
    return false;  // invalid header
144
  }
145
  for (auto i = _headers.begin(); i != _headers.end(); ++i) {
146
    if (i->name().equalsIgnoreCase(header.name())) {
147
      // header already set
148
      if (replaceExisting) {
149
        // remove, break and add the new one
150
        _headers.erase(i);
151
        break;
152
      } else if (headerMustBePresentOnce(i->name())) {  // we can have only one header with that name
153
        // do not update
154
        return false;
155
      } else {
156
        break;  // accept multiple headers with the same name
157
      }
158
    }
159
  }
160
  // header was not found found, or existing one was removed
161
  _headers.emplace_back(std::move(header));
162
  return true;
163
}
164
 
165
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
166
  for (auto i = _headers.begin(); i != _headers.end(); ++i) {
167
    if (i->name().equalsIgnoreCase(name)) {
168
      // header already set
169
      if (replaceExisting) {
170
        // remove, break and add the new one
171
        _headers.erase(i);
172
        break;
173
      } else if (headerMustBePresentOnce(i->name())) {  // we can have only one header with that name
174
        // do not update
175
        return false;
176
      } else {
177
        break;  // accept multiple headers with the same name
178
      }
179
    }
180
  }
181
  // header was not found found, or existing one was removed
182
  _headers.emplace_back(name, value);
183
  return true;
184
}
185
 
186
void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) {
187
  if (version) {
188
    addHeader(T_Accept_Ranges, T_none, false);
189
    if (_chunked) {
190
      addHeader(T_Transfer_Encoding, T_chunked, false);
191
    }
192
  }
193
 
194
  if (_sendContentLength) {
195
    addHeader(T_Content_Length, String(_contentLength), false);
196
  }
197
 
198
  if (_contentType.length()) {
199
    addHeader(T_Content_Type, _contentType.c_str(), false);
200
  }
201
 
202
  // precompute buffer size to avoid reallocations by String class
203
  size_t len = 0;
204
  len += 50;  // HTTP/1.1 200 <reason>\r\n
205
  for (const auto &header : _headers) {
206
    len += header.name().length() + header.value().length() + 4;
207
  }
208
 
209
  // prepare buffer
210
  buffer.reserve(len);
211
 
212
  // HTTP header
213
#ifdef ESP8266
214
  buffer.concat(PSTR("HTTP/1."));
215
#else
216
  buffer.concat("HTTP/1.");
217
#endif
218
  buffer.concat(version);
219
  buffer.concat(' ');
220
  buffer.concat(_code);
221
  buffer.concat(' ');
222
  buffer.concat(responseCodeToString(_code));
223
  buffer.concat(T_rn);
224
 
225
  // Add headers
226
  for (const auto &header : _headers) {
227
    buffer.concat(header.name());
228
#ifdef ESP8266
229
    buffer.concat(PSTR(": "));
230
#else
231
    buffer.concat(": ");
232
#endif
233
    buffer.concat(header.value());
234
    buffer.concat(T_rn);
235
  }
236
 
237
  buffer.concat(T_rn);
238
  _headLength = buffer.length();
239
}
240
 
241
bool AsyncWebServerResponse::_started() const {
242
  return _state > RESPONSE_SETUP;
243
}
244
bool AsyncWebServerResponse::_finished() const {
245
  return _state > RESPONSE_WAIT_ACK;
246
}
247
bool AsyncWebServerResponse::_failed() const {
248
  return _state == RESPONSE_FAILED;
249
}
250
bool AsyncWebServerResponse::_sourceValid() const {
251
  return false;
252
}
253
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) {
254
  _state = RESPONSE_END;
255
}
256
 
257
/*
258
 * String/Code Response
259
 * */
260
AsyncBasicResponse::AsyncBasicResponse(int code, const char *contentType, const char *content) {
261
  _code = code;
262
  _content = content;
263
  _contentType = contentType;
264
  if (_content.length()) {
265
    _contentLength = _content.length();
266
    if (!_contentType.length()) {
267
      _contentType = T_text_plain;
268
    }
269
  }
270
  addHeader(T_Connection, T_close, false);
271
}
272
 
273
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) {
274
  _state = RESPONSE_HEADERS;
275
  _assembleHead(_assembled_headers, request->version());
276
  write_send_buffs(request, 0, 0);
277
}
278
 
279
size_t AsyncBasicResponse::write_send_buffs(AsyncWebServerRequest *request, size_t len, uint32_t time) {
280
  (void)time;
281
 
282
  // this is not functionally needed in AsyncBasicResponse itself, but kept for compatibility if some of the derived classes are rely on it somehow
283
  _ackedLength += len;
284
  size_t payloadlen{0};  // amount of data to be written to tcp sockbuff during this call, used as return value of this method
285
 
286
  // send http headers first
287
  if (_state == RESPONSE_HEADERS) {
288
    // copy headers buffer to sock buffer
289
    size_t const pcb_written = request->client()->add(_assembled_headers.c_str() + _writtenHeadersLength, _assembled_headers.length() - _writtenHeadersLength);
290
    _writtenLength += pcb_written;
291
    _writtenHeadersLength += pcb_written;
292
    if (_writtenHeadersLength < _assembled_headers.length()) {
293
      // we were not able to fit all headers in current buff, send this part here and return later for the rest
294
      if (!request->client()->send()) {
295
        // something is wrong, what should we do here?
296
        request->client()->close();
297
        return 0;
298
      }
299
      return pcb_written;
300
    }
301
    // otherwise we've added all the (remainder) headers in current buff, go on with content
302
    _state = RESPONSE_CONTENT;
303
    payloadlen += pcb_written;
304
    _assembled_headers = String();  // clear
305
  }
306
 
307
  if (_state == RESPONSE_CONTENT) {
308
    size_t const pcb_written = request->client()->write(_content.c_str() + _sentLength, _content.length() - _sentLength);
309
    _writtenLength += pcb_written;  // total written data (hdrs + body)
310
    _sentLength += pcb_written;     // body written data
311
    payloadlen += pcb_written;      // data writtent in current buff
312
    if (_sentLength >= _content.length()) {
313
      // we've just sent all the (remainder) data in current buff, complete the response
314
      _state = RESPONSE_END;
315
    }
316
  }
317
 
318
  // implicit complete
319
  if (_state == RESPONSE_WAIT_ACK) {
320
    _state = RESPONSE_END;
321
  }
322
 
323
  return payloadlen;
324
}
325
 
326
/*
327
 * Abstract Response
328
 *
329
 */
330
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _callback(callback) {
331
  // In case of template processing, we're unable to determine real response size
332
  if (callback) {
333
    _contentLength = 0;
334
    _sendContentLength = false;
335
    _chunked = true;
336
  }
337
}
338
 
339
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) {
340
  addHeader(T_Connection, T_close, false);
341
  _assembleHead(_assembled_headers, request->version());
342
  _state = RESPONSE_HEADERS;
343
  write_send_buffs(request, 0, 0);
344
}
345
 
346
size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, size_t len, uint32_t time) {
347
  (void)time;
348
  if (!_sourceValid()) {
349
    _state = RESPONSE_FAILED;
350
    request->client()->close();
351
    return 0;
352
  }
353
 
354
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
355
  /*
356
    for response payloads with unknown length or length larger than TCP_WND we need to control AsyncTCP's queue and in-flight fragmentation.
357
    Either user callback could fill buffer with very small chunks or long running large response could receive a lot of poll() calls here,
358
    both could flood asynctcp's queue with large number of events to handle and fragment socket buffer space for large responses.
359
    Let's ignore polled acks and acks in case when available window size is less than our used buffer size since we won't be able to fill and send it whole
360
    That way we could balance on having at least half tcp win in-flight while minimizing send/ack events in asynctcp Q
361
    This could decrease sustained bandwidth for one single connection but would drastically improve parallelism and equalize bandwidth sharing
362
  */
363
  // return a credit for each chunk of acked data (polls does not give any credits)
364
  if (len) {
365
    ++_in_flight_credit;
366
    _in_flight -= std::min(len, _in_flight);
367
  }
368
 
369
  if (_chunked || !_sendContentLength || (_sentLength > CONFIG_LWIP_TCP_WND_DEFAULT)) {
370
    if (!_in_flight_credit || (ASYNC_RESPONCE_BUFF_SIZE > request->client()->space())) {
371
      // async_ws_log_d("defer user call in_flight:%u, tcpwin:%u", _in_flight, request->client()->space());
372
      // take the credit back since we are ignoring this ack and rely on other inflight data acks
373
      if (len) {
374
        --_in_flight_credit;
375
      }
376
      return 0;
377
    }
378
  }
379
#endif
380
 
381
  // this is not functionally needed in AsyncAbstractResponse itself, but kept for compatibility if some of the derived classes are rely on it somehow
382
  _ackedLength += len;
383
 
384
  size_t payloadlen{0};  // amount of data to be written to tcp sockbuff during this call, used as return value of this method
385
 
386
  // send http headers first
387
  if (_state == RESPONSE_HEADERS) {
388
    // copy headers buffer to sock buffer
389
    size_t const pcb_written = request->client()->add(_assembled_headers.c_str() + _writtenHeadersLength, _assembled_headers.length() - _writtenHeadersLength);
390
    _writtenLength += pcb_written;
391
    _writtenHeadersLength += pcb_written;
392
    if (_writtenHeadersLength < _assembled_headers.length()) {
393
// we were not able to fit all headers in current buff, send this part here and return later for the rest
394
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
395
      _in_flight += pcb_written;
396
      --_in_flight_credit;  // take a credit
397
#endif
398
      if (!request->client()->send()) {
399
        // something is wrong, what should we do here?
400
        request->client()->close();
401
        return 0;
402
      }
403
      return pcb_written;
404
    }
405
    // otherwise we've added all the (remainder) headers in current buff
406
    _state = RESPONSE_CONTENT;
407
    payloadlen += pcb_written;
408
    _assembled_headers = String();  // clear
409
  }
410
 
411
  // send content body
412
  if (_state == RESPONSE_CONTENT) {
413
    do {
414
      if (_send_buffer_len && _send_buffer) {
415
        // data is pending in buffer from a previous call or previous iteration
416
        size_t const added_len =
417
          request->client()->add(reinterpret_cast<char *>(_send_buffer->data() + _send_buffer_offset), _send_buffer_len - _send_buffer_offset);
418
        if (added_len != _send_buffer_len - _send_buffer_offset) {
419
          // we were not able to add entire buffer's content to tcp buffs, leave it for later
420
          // (this should not happen normally unless connection's TCP window suddenly changed from remote or mem pressure)
421
          _send_buffer_offset += added_len;
422
          break;
423
        } else {
424
          _send_buffer_len = _send_buffer_offset = 0;  // consider buffer empty
425
        }
426
        payloadlen += added_len;
427
      }
428
 
429
      auto tcp_win = request->client()->space();
430
      if (tcp_win == 0 || _state == RESPONSE_END) {
431
        break;  // no room left or no more data
432
      }
433
 
434
      if ((_chunked || !_sendContentLength) && (tcp_win < CONFIG_LWIP_TCP_MSS / 2)) {
435
        // available window size is not enough to send a new chunk sized half of tcp mss, let's wait for better chance and reduce pressure to AsyncTCP's event Q
436
        break;
437
      }
438
 
439
      if (!_send_buffer) {
440
        auto p = new (std::nothrow) std::array<uint8_t, ASYNC_RESPONCE_BUFF_SIZE>;
441
        if (p) {
442
          _send_buffer.reset(p);
443
          _send_buffer_len = _send_buffer_offset = 0;
444
        } else {
445
          break;  // OOM
446
        }
447
      }
448
 
449
      if (_chunked) {
450
        // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
451
        // See https://datatracker.ietf.org/doc/html/rfc9112#section-7.1
452
        size_t const readLen =
453
          _fillBufferAndProcessTemplates(_send_buffer->data() + 6, std::min(_send_buffer->size(), tcp_win) - 8);  // reserve 8 bytes for chunk size data
454
        if (readLen != RESPONSE_TRY_AGAIN) {
455
          // Write 4 hex digits directly without null terminator
456
          static constexpr char hexChars[] = "0123456789abcdef";
457
          _send_buffer->data()[0] = hexChars[(readLen >> 12) & 0xF];
458
          _send_buffer->data()[1] = hexChars[(readLen >> 8) & 0xF];
459
          _send_buffer->data()[2] = hexChars[(readLen >> 4) & 0xF];
460
          _send_buffer->data()[3] = hexChars[readLen & 0xF];
461
          _send_buffer->data()[4] = '\r';
462
          _send_buffer->data()[5] = '\n';
463
          // data (readLen bytes) is already there
464
          _send_buffer->at(readLen + 6) = '\r';
465
          _send_buffer->at(readLen + 7) = '\n';
466
          _send_buffer_len += readLen + 8;  // set buffers's size to match added data
467
          _sentLength += readLen;           // data is not sent yet, but we won't get a chance to count this later properly for chunked data
468
          if (!readLen) {
469
            // last chunk?
470
            _state = RESPONSE_END;
471
          }
472
        }
473
      } else {
474
        // Non-chunked data. We can either have a response:
475
        // - with a known content-length (example: Json response), in that case we pass the remaining length if lower than tcp_win
476
        // - or with unknown content-length (see LargeResponse example, like ESP32Cam with streaming), in that case we just fill as much as tcp_win allows
477
        size_t maxLen = std::min(_send_buffer->size(), tcp_win);
478
        if (_contentLength) {
479
          maxLen = _contentLength > _sentLength ? std::min(maxLen, _contentLength - _sentLength) : 0;
480
        }
481
 
482
        size_t const readLen = _fillBufferAndProcessTemplates(_send_buffer->data(), maxLen);
483
 
484
        if (readLen == 0) {
485
          // no more data to send
486
          _state = RESPONSE_END;
487
        } else if (readLen != RESPONSE_TRY_AGAIN) {
488
          _send_buffer_len += readLen;  // set buffers's size to match added data
489
          _sentLength += readLen;       // data is not sent yet, but we need it to understand that it would be last block
490
          if (_sendContentLength && (_sentLength == _contentLength)) {
491
            // it was last piece of content
492
            _state = RESPONSE_END;
493
          }
494
        }
495
      }
496
    } while (_send_buffer_len);  // go on till we have something in buffer pending to send
497
 
498
    // execute sending whatever we have in sock buffs now
499
    request->client()->send();
500
    _writtenLength += payloadlen;
501
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
502
    _in_flight += payloadlen;
503
    --_in_flight_credit;  // take a credit
504
#endif
505
    if (_send_buffer_len == 0) {
506
      // buffer empty, we can release mem, otherwise need to keep it till next run (should not happen under normal conditions)
507
      _send_buffer.reset();
508
    }
509
    return payloadlen;
510
  }  // (_state == RESPONSE_CONTENT)
511
 
512
  // implicit check
513
  if (_state == RESPONSE_WAIT_ACK) {
514
    // we do not need to wait for any acks actually if we won't send any more data,
515
    // connection would be closed gracefully with last piece of data (in AsyncWebServerRequest::_onAck)
516
    _state = RESPONSE_END;
517
  }
518
  return 0;
519
}
520
 
521
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t *data, const size_t len) {
522
  // If we have something in cache, copy it to buffer
523
  const size_t readFromCache = std::min(len, _cache.size());
524
  if (readFromCache) {
525
    memcpy(data, _cache.data(), readFromCache);
526
    _cache.erase(_cache.begin(), _cache.begin() + readFromCache);
527
  }
528
  // If we need to read more...
529
  const size_t needFromFile = len - readFromCache;
530
  const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
531
  return readFromCache + readFromContent;
532
}
533
 
534
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size_t len) {
535
  if (!_callback) {
536
    return _fillBuffer(data, len);
537
  }
538
 
539
  const size_t originalLen = len;
540
  len = _readDataFromCacheOrContent(data, len);
541
  // Now we've read 'len' bytes, either from cache or from file
542
  // Search for template placeholders
543
  uint8_t *pTemplateStart = data;
544
  while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) {
545
    // data[0] ... data[len - 1]
546
    uint8_t *pTemplateEnd =
547
      (pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
548
    // temporary buffer to hold parameter name
549
    uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
550
    String paramName;
551
    // If closing placeholder is found:
552
    if (pTemplateEnd) {
553
      // prepare argument to callback
554
      const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
555
      if (paramNameLength) {
556
        memcpy(buf, pTemplateStart + 1, paramNameLength);
557
        buf[paramNameLength] = 0;
558
        paramName = String(reinterpret_cast<char *>(buf));
559
      } else {  // double percent sign encountered, this is single percent sign escaped.
560
        // remove the 2nd percent sign
561
        memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
562
        len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
563
        ++pTemplateStart;
564
      }
565
    } else if (&data[len - 1] - pTemplateStart + 1
566
               < TEMPLATE_PARAM_NAME_LENGTH + 2) {  // closing placeholder not found, check if it's in the remaining file data
567
      memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
568
      const size_t readFromCacheOrContent =
569
        _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
570
      if (readFromCacheOrContent) {
571
        pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
572
        if (pTemplateEnd) {
573
          // prepare argument to callback
574
          *pTemplateEnd = 0;
575
          paramName = String(reinterpret_cast<char *>(buf));
576
          // Copy remaining read-ahead data into cache
577
          _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
578
          pTemplateEnd = &data[len - 1];
579
        } else  // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
580
        {
581
          // but first, store read file data in cache
582
          _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
583
          ++pTemplateStart;
584
        }
585
      } else {  // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
586
        ++pTemplateStart;
587
      }
588
    } else {  // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
589
      ++pTemplateStart;
590
    }
591
    if (paramName.length()) {
592
      // call callback and replace with result.
593
      // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
594
      // Data after pTemplateEnd may need to be moved.
595
      // The first byte of data after placeholder is located at pTemplateEnd + 1.
596
      // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
597
      const String paramValue(_callback(paramName));
598
      const char *pvstr = paramValue.c_str();
599
      const unsigned int pvlen = paramValue.length();
600
      const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
601
      // make room for param value
602
      // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
603
      if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
604
        _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
605
        // 2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
606
        memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
607
        len = originalLen;  // fix issue with truncated data, not sure if it has any side effects
608
      } else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) {
609
        // 2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
610
        //    Move the entire data after the placeholder
611
        memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
612
      }
613
      // 3. replace placeholder with actual value
614
      memcpy(pTemplateStart, pvstr, numBytesCopied);
615
      // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
616
      if (numBytesCopied < pvlen) {
617
        _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
618
      } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) {  // result is copied fully; if result is shorter than placeholder text...
619
        // there is some free room, fill it from cache
620
        const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
621
        const size_t totalFreeRoom = originalLen - len + roomFreed;
622
        len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
623
      } else {  // result is copied fully; it is longer than placeholder text
624
        const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
625
        len = std::min(len + roomTaken, originalLen);
626
      }
627
    }
628
  }  // while(pTemplateStart)
629
  return len;
630
}
631
 
632
/*
633
 * File Response
634
 * */
635
 
636
/**
637
 * @brief Sets the content type based on the file path extension
638
 *
639
 * This method determines the appropriate MIME content type for a file based on its
640
 * file extension. It supports both external content type functions (if available)
641
 * and an internal mapping of common file extensions to their corresponding MIME types.
642
 *
643
 * @param path The file path string from which to extract the extension
644
 * @note The method modifies the internal _contentType member variable
645
 */
646
void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
647
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
648
#ifndef ESP8266
649
  extern const char *getContentType(const String &path);
650
#else
651
  extern const __FlashStringHelper *getContentType(const String &path);
652
#endif
653
  _contentType = getContentType(path);
654
#else
655
  const char *cpath = path.c_str();
656
  const char *dot = strrchr(cpath, '.');
657
 
658
  if (!dot) {
659
    _contentType = T_application_octet_stream;
660
    return;
661
  }
662
 
663
  if (strcmp(dot, T__html) == 0 || strcmp(dot, T__htm) == 0) {
664
    _contentType = T_text_html;
665
  } else if (strcmp(dot, T__css) == 0) {
666
    _contentType = T_text_css;
667
  } else if (strcmp(dot, T__js) == 0 || strcmp(dot, T__mjs) == 0) {
668
    _contentType = T_text_javascript;
669
  } else if (strcmp(dot, T__json) == 0) {
670
    _contentType = T_application_json;
671
  } else if (strcmp(dot, T__png) == 0) {
672
    _contentType = T_image_png;
673
  } else if (strcmp(dot, T__ico) == 0) {
674
    _contentType = T_image_x_icon;
675
  } else if (strcmp(dot, T__svg) == 0) {
676
    _contentType = T_image_svg_xml;
677
  } else if (strcmp(dot, T__jpg) == 0) {
678
    _contentType = T_image_jpeg;
679
  } else if (strcmp(dot, T__webp) == 0) {
680
    _contentType = T_image_webp;
681
  } else if (strcmp(dot, T__avif) == 0) {
682
    _contentType = T_image_avif;
683
  } else if (strcmp(dot, T__gif) == 0) {
684
    _contentType = T_image_gif;
685
  } else if (strcmp(dot, T__woff2) == 0) {
686
    _contentType = T_font_woff2;
687
  } else if (strcmp(dot, T__woff) == 0) {
688
    _contentType = T_font_woff;
689
  } else if (strcmp(dot, T__ttf) == 0) {
690
    _contentType = T_font_ttf;
691
  } else if (strcmp(dot, T__xml) == 0) {
692
    _contentType = T_text_xml;
693
  } else if (strcmp(dot, T__pdf) == 0) {
694
    _contentType = T_application_pdf;
695
  } else if (strcmp(dot, T__mp4) == 0) {
696
    _contentType = T_video_mp4;
697
  } else if (strcmp(dot, T__opus) == 0) {
698
    _contentType = T_audio_opus;
699
  } else if (strcmp(dot, T__webm) == 0) {
700
    _contentType = T_video_webm;
701
  } else if (strcmp(dot, T__txt) == 0) {
702
    _contentType = T_text_plain;
703
  } else {
704
    _contentType = T_application_octet_stream;
705
  }
706
#endif
707
}
708
 
709
/**
710
 * @brief Constructor for AsyncFileResponse that handles file serving with compression support
711
 *
712
 * This constructor creates an AsyncFileResponse object that can serve files from a filesystem,
713
 * with automatic fallback to gzip-compressed versions if the original file is not found.
714
 * It also handles ETag generation for caching and supports both inline and download modes.
715
 *
716
 * @param fs Reference to the filesystem object used to open files
717
 * @param path Path to the file to be served (without compression extension)
718
 * @param contentType MIME type of the file content (empty string for auto-detection)
719
 * @param download If true, file will be served as download attachment; if false, as inline content
720
 * @param callback Template processor callback for dynamic content processing
721
 */
722
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
723
  : AsyncAbstractResponse(callback) {
724
 
725
  // Try to open the uncompressed version first
726
  _content = fs.open(path, fs::FileOpenMode::read);
727
  if (!_content.available()) {
728
    // If not available try to open the compressed version (.gz)
729
    String gzPath;
730
    uint16_t pathLen = path.length();
731
    gzPath.reserve(pathLen + 3);
732
    gzPath.concat(path);
733
    gzPath.concat(asyncsrv::T__gz);
734
    _content = fs.open(gzPath, fs::FileOpenMode::read);
735
 
736
    char serverETag[11];
737
    if (AsyncWebServerRequest::_getEtag(_content, serverETag)) {
738
      addHeader(T_Content_Encoding, T_gzip, false);
739
      _callback = nullptr;  // Unable to process zipped templates
740
      _sendContentLength = true;
741
      _chunked = false;
742
 
743
      // Add ETag and cache headers
744
      addHeader(T_ETag, serverETag, true);
745
      addHeader(T_Cache_Control, T_no_cache, true);
746
 
747
      _content.seek(0);
748
    } else {
749
      // File is corrupted or invalid
750
      _code = 404;
751
      return;
752
    }
753
  }
754
 
755
  _contentLength = _content.size();
756
 
757
  if (*contentType == '\0') {
758
    _setContentTypeFromPath(path);
759
  } else {
760
    _contentType = contentType;
761
  }
762
 
763
  if (download) {
764
    // Extract filename from path and set as download attachment
765
    int filenameStart = path.lastIndexOf('/') + 1;
766
    const char *filename = path.c_str() + filenameStart;
767
    String buf;
768
    buf.reserve(sizeof(T_attachment) - 1 + strlen(filename) + 2);
769
    buf = T_attachment;
770
    buf += filename;
771
    buf += "\"";
772
    addHeader(T_Content_Disposition, buf, false);
773
  } else {
774
    // Serve file inline (display in browser)
775
    addHeader(T_Content_Disposition, T_inline, false);
776
  }
777
 
778
  _code = 200;
779
}
780
 
781
AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
782
  : AsyncAbstractResponse(callback) {
783
  _code = 200;
784
 
785
  if (String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
786
    addHeader(T_Content_Encoding, T_gzip, false);
787
    _callback = nullptr;  // Unable to process gzipped templates
788
    _sendContentLength = true;
789
    _chunked = false;
790
  }
791
 
792
  _content = content;
793
  _contentLength = _content.size();
794
 
795
  if (*contentType == '\0') {
796
    _setContentTypeFromPath(path);
797
  } else {
798
    _contentType = contentType;
799
  }
800
 
801
  if (download) {
802
    // Extract filename from path and set as download attachment
803
    int filenameStart = path.lastIndexOf('/') + 1;
804
    const char *filename = path.c_str() + filenameStart;
805
    String buf;
806
    buf.reserve(sizeof(T_attachment) - 1 + strlen(filename) + 2);
807
    buf = T_attachment;
808
    buf += filename;
809
    buf += "\"";
810
    addHeader(T_Content_Disposition, buf, false);
811
  } else {
812
    // Serve file inline (display in browser)
813
    addHeader(T_Content_Disposition, T_inline, false);
814
  }
815
}
816
 
817
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len) {
818
  return _content.read(data, len);
819
}
820
 
821
/*
822
 * Stream Response
823
 * */
824
 
825
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
826
  _code = 200;
827
  _content = &stream;
828
  _contentLength = len;
829
  _contentType = contentType;
830
}
831
 
832
size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len) {
833
  size_t available = _content->available();
834
  size_t outLen = (available > len) ? len : available;
835
  size_t i;
836
  for (i = 0; i < outLen; i++) {
837
    data[i] = _content->read();
838
  }
839
  return outLen;
840
}
841
 
842
/*
843
 * Callback Response
844
 * */
845
 
846
AsyncCallbackResponse::AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
847
  : AsyncAbstractResponse(templateCallback) {
848
  _code = 200;
849
  _content = callback;
850
  _contentLength = len;
851
  if (!len) {
852
    _sendContentLength = false;
853
  }
854
  _contentType = contentType;
855
  _filledLength = 0;
856
}
857
 
858
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len) {
859
  size_t ret = _content(data, len, _filledLength);
860
  if (ret != RESPONSE_TRY_AGAIN) {
861
    _filledLength += ret;
862
  }
863
  return ret;
864
}
865
 
866
/*
867
 * Chunked Response
868
 * */
869
 
870
AsyncChunkedResponse::AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
871
  : AsyncAbstractResponse(processorCallback) {
872
  _code = 200;
873
  _content = callback;
874
  _contentLength = 0;
875
  _contentType = contentType;
876
  _sendContentLength = false;
877
  _chunked = true;
878
  _filledLength = 0;
879
}
880
 
881
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len) {
882
  size_t ret = _content(data, len, _filledLength);
883
  if (ret != RESPONSE_TRY_AGAIN) {
884
    _filledLength += ret;
885
  }
886
  return ret;
887
}
888
 
889
/*
890
 * Progmem Response
891
 * */
892
 
893
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback)
894
  : AsyncAbstractResponse(callback), _content(content), _index(0) {
895
  _code = code;
896
  _contentType = contentType;
897
  _contentLength = len;
898
}
899
 
900
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len) {
901
  size_t read_size = std::min(len, _contentLength - _index);
902
  memcpy_P(data, _content + _index, read_size);
903
  _index += read_size;
904
  return read_size;
905
}
906
 
907
/*
908
 * Response Stream (You can print/write/printf to it, up to the contentLen bytes)
909
 * */
910
 
911
AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferSize) {
912
  _code = 200;
913
  _contentLength = 0;
914
  _contentType = contentType;
915
  // internal buffer will be null on allocation failure
916
  _content = std::unique_ptr<cbuf>(new cbuf(bufferSize));
917
  if (bufferSize && _content->size() < bufferSize) {
918
    async_ws_log_e("Failed to allocate");
919
  }
920
}
921
 
922
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
923
  return _content->read((char *)buf, maxLen);
924
}
925
 
926
size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
927
  if (_started()) {
928
    return 0;
929
  }
930
  if (len > _content->room()) {
931
    size_t needed = len - _content->room();
932
    _content->resizeAdd(needed);
933
    // log a warning if allocation failed, but do not return: keep writing the bytes we can
934
    // with _content->write: if len is more than the available size in the buffer, only
935
    // the available size will be written
936
    if (len > _content->room()) {
937
      async_ws_log_e("Failed to allocate");
938
    }
939
  }
940
  size_t written = _content->write((const char *)data, len);
941
  _contentLength += written;
942
  return written;
943
}
944
 
945
size_t AsyncResponseStream::write(uint8_t data) {
946
  return write(&data, 1);
947
}