| 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 |
}
|