29#if DETWS_ENABLE_FILE_SERVING
40 if (!gmtime_r(&t, &tmv))
42 strftime(out, cap,
"%a, %d %b %Y %H:%M:%S GMT", &tmv);
51static bool http_not_modified_since(time_t mtime,
const char *ims)
53 if (mtime <= 0 || !ims)
62 if (sscanf(ims,
"%*3s, %d %3s %d %d:%d:%d", &day, mon, &year, &hh, &mm, &ss) != 6)
64 static const char MONTHS[] =
"JanFebMarAprMayJunJulAugSepOctNovDec";
65 const char *mp = strstr(MONTHS, mon);
68 if (!mp || ((mp - MONTHS) % 3) != 0)
70 int imon = (int)(mp - MONTHS) / 3;
73 if (!gmtime_r(&mtime, &tf))
76 int fy = tf.tm_year + 1900;
79 if (tf.tm_mon != imon)
80 return tf.tm_mon < imon;
81 if (tf.tm_mday != day)
82 return tf.tm_mday < day;
84 return tf.tm_hour < hh;
86 return tf.tm_min < mm;
87 return tf.tm_sec <= ss;
93static bool inm_matches(
const char *inm,
const char *etag)
95 while (*inm ==
' ' || *inm ==
'\t')
99 size_t etlen = strnlen(etag, 40);
103 while (*p ==
' ' || *p ==
'\t' || *p ==
',')
108 if (tag[0] ==
'W' && tag[1] ==
'/')
112 const char *end = strchr(tag + 1,
'"');
113 if (end && (
size_t)(end - tag + 1) == etlen && strncmp(tag, etag, etlen) == 0)
116 const char *comma = strchr(p,
',');
124void DetWebServer::serve_file_internal(uint8_t slot_id,
bool head, fs::FS &file_sys,
const char *fs_path,
125 const char *content_type,
const char *content_encoding)
127 fs::File f = file_sys.open(fs_path,
"r");
130 send(slot_id, 404, DET_MIME_TEXT_PLAIN,
"Not Found");
134 if (!det_conn_active(slot_id))
141 size_t file_size = f.size();
144 const char *cl = resp_conn_hdr(slot_id, &keep);
149 if (content_encoding)
150 snprintf(enc_line,
sizeof(enc_line),
"Content-Encoding: %s\r\n", content_encoding);
157 time_t mtime = f.getLastWrite();
159 snprintf(etag,
sizeof(etag),
"\"%x-%lx\"", (
unsigned)file_size, (
unsigned long)mtime);
162 char lastmod_line[17 +
sizeof(lm_date)];
163 lastmod_line[0] =
'\0';
166 snprintf(lastmod_line,
sizeof(lastmod_line),
"Last-Modified: %s\r\n", lm_date);
169 bool not_modified = inm ? inm_matches(inm, etag)
175 int n304 = snprintf(h304,
sizeof(h304),
"HTTP/1.1 304 Not Modified\r\nETag: %s\r\n%s%s%s%s\r\n", etag,
176 lastmod_line, _cache_control_buf, _cors_enabled ? _cors_header_buf :
"", cl);
178 resp_end(slot_id, 304, 0, keep,
true);
182 snprintf(etag_line,
sizeof(etag_line),
"ETag: %s\r\n", etag);
184 const char *etag_line =
"";
185 const char *lastmod_line =
"";
190 size_t body_len = file_size;
192 const char *accept_ranges =
"";
194 range_line[0] =
'\0';
196#if DETWS_ENABLE_RANGE
197 accept_ranges =
"Accept-Ranges: bytes\r\n";
206 int n416 = snprintf(h416,
sizeof(h416),
207 "HTTP/1.1 416 Range Not Satisfiable\r\n"
208 "Content-Range: bytes */%u\r\n"
209 "Content-Length: 0\r\n"
211 (
unsigned)file_size, _cors_enabled ? _cors_header_buf :
"", cl);
213 resp_end(slot_id, 416, 0, keep,
true);
219 body_len = r_end - r_start + 1;
220 snprintf(range_line,
sizeof(range_line),
"Content-Range: bytes %u-%u/%u\r\n", (
unsigned)r_start,
221 (
unsigned)r_end, (
unsigned)file_size);
222 f.seek((uint32_t)r_start);
229 snprintf(header,
sizeof(header),
231 "Content-Type: %s\r\n"
232 "Content-Length: %u\r\n"
235 status,
status_text(status), content_type, (
unsigned)body_len, accept_ranges, range_line, enc_line,
236 etag_line, lastmod_line, _cache_control_buf, _cors_enabled ? _cors_header_buf :
"", cl);
241 if (head || body_len == 0)
244 resp_end(slot_id, status, 0, keep);
252 FileSend &s =
s_send.file[slot_id];
255 s.remaining = body_len;
257 s.total = (int)body_len;
260 file_send_pump(slot_id);
267void DetWebServer::file_send_pump(uint8_t slot_id)
269 FileSend &s =
s_send.file[slot_id];
273 if (!det_conn_active(slot_id))
286 while (s.remaining > 0)
294 size_t want = s.remaining <
sizeof(chunk) ? s.remaining : sizeof(chunk);
297 size_t n = s.file.read(chunk, want);
305 s.file.seek((uint32_t)s.off);
317 resp_end(slot_id, s.status, s.total, s.keep);
320void DetWebServer::serve_file(uint8_t slot_id, fs::FS &file_sys,
const char *fs_path,
const char *content_type)
322 serve_file_internal(slot_id,
req_is_head(slot_id), file_sys, fs_path, content_type,
nullptr);
325void DetWebServer::serve_static(
const char *url_prefix, fs::FS &file_sys,
const char *fs_root)
329 Route *r = &_routes[_route_count++];
334 if (n > 0 && url_prefix[n - 1] ==
'*')
335 snprintf(pat,
sizeof(pat),
"%s", url_prefix);
337 snprintf(pat,
sizeof(pat),
"%s*", url_prefix);
339 r->
type = RouteType::ROUTE_STATIC;
341 r->static_fs = &file_sys;
342 r->static_root = fs_root;
345void DetWebServer::serve_static_request(uint8_t slot_id,
HttpReq *req,
const Route *r)
349 send(slot_id, 404, DET_MIME_TEXT_PLAIN,
"Not Found");
355 if (plen > 0 && r->
path[plen - 1] ==
'*')
360 if (strstr(sub,
".."))
362 send(slot_id, 404, DET_MIME_TEXT_PLAIN,
"Not Found");
366 const char *root = r->static_root ? r->static_root :
"";
368 bool root_slash = (rlen > 0 && root[rlen - 1] ==
'/');
369 if (root_slash && sub[0] ==
'/')
371 bool sub_slash = (sub[0] ==
'/');
372 const char *sep = (root_slash || sub_slash) ?
"" :
"/";
376 bool dir = (slen == 0) || (sub[slen - 1] ==
'/');
379 int wn = dir ? snprintf(fs_path,
sizeof(fs_path),
"%s%s%sindex.html", root, sep, sub)
380 : snprintf(fs_path, sizeof(fs_path),
"%s%s%s", root, sep, sub);
381 if (wn <= 0 || wn >= (
int)
sizeof(fs_path))
383 send(slot_id, 404, DET_MIME_TEXT_PLAIN,
"Not Found");
393 if (ae && strstr(ae,
"gzip"))
396 int gn = snprintf(gz,
sizeof(gz),
"%s.gz", fs_path);
397 if (gn > 0 && gn < (
int)
sizeof(gz) && r->static_fs->exists(gz))
399 serve_file_internal(slot_id, head, *r->static_fs, gz, ctype,
"gzip");
404 serve_file_internal(slot_id, head, *r->static_fs, fs_path, ctype,
nullptr);
#define RESP_HDR_BUF_SIZE
Stack buffer for HTTP response header lines in send() / send_empty() / send_unauth() / serve_file().
#define FILE_CHUNK_SIZE
Bytes read from the filesystem and passed to tcp_write() per loop().
#define MAX_PATH_LEN
Maximum URL path length (including leading /).
#define MAX_ROUTES
Maximum simultaneously registered routes.
void send(uint8_t slot_id, int code, const char *content_type, const char *payload)
Send an HTTP response with a body and close the connection.
static const char * mime_type(const char *path)
Guess a Content-Type from a path's file extension.
void fill_route_base(Route *r, const char *path)
Register a route in the route table.
const char * status_text(int code)
Convert an HTTP status code to its standard reason phrase.
bool req_is_head(uint8_t slot_id)
True if the request in slot slot_id used the HEAD method (send headers, no body).
Layer 7 (Application) - public HTTP routing API.
@ HTTP_GET
Safe, idempotent read.
Library-private declarations shared between dwserver.cpp and the src/server/*.cpp request-handler tra...
void http_rfc1123(time_t t, char *out, size_t cap)
Format t as an RFC 1123 GMT date into out (cap bytes); out is emptied for t <= 0.
const char * http_get_header(const HttpReq *req, const char *key)
Look up a header value by name (case-insensitive).
HttpReq http_pool[CONN_POOL_SLOTS]
Pool of parser contexts, one per connection-pool slot (incl. reserved dispatch slots).
Shared single-range Range: bytes=... parser (RFC 7233), used by static file serving and the edge cach...
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
void http_reset(uint8_t slot_id)
Reset the HTTP parser for a connection slot.
Fully-parsed HTTP/1.1 request.
char path[MAX_PATH_LEN]
URL path, null-terminated; no query string.
Internal route entry stored in the routing table.
RouteType type
HTTP, WS, or SSE.
HttpMethod method
HTTP method (RouteType::ROUTE_HTTP only).
char path[MAX_PATH_LEN]
Null-terminated path pattern.
bool det_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
void det_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
u16_t det_conn_sndbuf(uint8_t slot)
Bytes that can currently be queued for sending on slot.
bool det_conn_send_flush(uint8_t slot, const void *data, u16_t len)
Send len bytes on slot and flush in a single tcpip_thread round-trip.
void det_conn_touch_active(uint8_t slot_id)
Refresh slot's idle-timeout timestamp while a response body is in flight.
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.