DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
upload_service.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file upload_service.cpp
6 * @brief Streaming file upload: POST body -> Arduino FS file (DETWS_ENABLE_UPLOAD).
7 */
8
9#include "upload_service.h"
10
11#if DETWS_ENABLE_UPLOAD
12
13#include "dwserver.h"
16#include <stdio.h>
17#include <string.h>
18
19// All upload-service state, owned by one instance (internal linkage): the server handle, the
20// route path, the destination filesystem + path, and the per-upload file/flags/counter (one
21// upload at a time on this single-task device). Grouped so it is one named owner, cross-TU
22// unreachable.
23struct UploadCtx
24{
25 DetWebServer *server = nullptr;
26 const char *path = nullptr;
27 fs::FS *fs = nullptr;
28 const char *dest = nullptr;
29 fs::File file;
30 bool active = false; ///< Destination file opened for the current upload.
31 bool error = false; ///< A write failed during the current upload.
32 size_t written = 0; ///< Bytes written so far / in the last upload.
33};
34static UploadCtx s_upl;
35
36/// @brief Stream-begin hook: accept POST @p s_upl.path and open the destination file.
37static bool upload_stream_begin(HttpReq *req)
38{
39 if (strcmp(req->method, "POST") != 0)
40 return false;
41 if (!s_upl.path || strcmp(req->path, s_upl.path) != 0)
42 return false;
43
44 s_upl.active = false;
45 s_upl.error = false;
46 s_upl.written = 0;
47 if (s_upl.fs && s_upl.dest)
48 {
49 s_upl.file = s_upl.fs->open(s_upl.dest, "w");
50 if (s_upl.file)
51 s_upl.active = true;
52 else
53 s_upl.error = true;
54 }
55 // Stream regardless so the body is consumed and the route handler can reply.
56 return true;
57}
58
59/// @brief Stream-data hook: write one body chunk to the destination file.
60static void upload_stream_data(HttpReq *req, const uint8_t *data, size_t len)
61{
62 (void)req; // a single upload streams at a time
63 if (s_upl.active && !s_upl.error)
64 {
65 if (s_upl.file.write(data, len) != len)
66 s_upl.error = true;
67 else
68 s_upl.written += len;
69 }
70}
71
72/// @brief Route handler (runs at ParseState::PARSE_COMPLETE): close the file and reply.
73static void upload_handle(uint8_t slot_id, HttpReq *req)
74{
75 if (!req->body_streaming)
76 {
77 s_upl.server->send(slot_id, 400, DET_MIME_TEXT_PLAIN, "POST a file body");
78 return;
79 }
80 if (s_upl.active)
81 s_upl.file.close();
82 if (!s_upl.active || s_upl.error)
83 {
84 s_upl.server->send(slot_id, 500, DET_MIME_TEXT_PLAIN, "upload failed");
85 return;
86 }
87 char msg[48];
88 snprintf(msg, sizeof(msg), "OK %u bytes", (unsigned)s_upl.written);
89 s_upl.server->send(slot_id, 200, DET_MIME_TEXT_PLAIN, msg);
90}
91
92size_t detws_upload_last_size()
93{
94 return s_upl.written;
95}
96
97void detws_upload_begin(DetWebServer &server, const char *path, fs::FS &fs, const char *dest_path)
98{
99 s_upl.server = &server;
100 s_upl.path = path;
101 s_upl.fs = &fs;
102 s_upl.dest = dest_path;
103
104 http_parser_set_stream_hooks(upload_stream_begin, upload_stream_data);
105 server.on(path, HttpMethod::HTTP_POST, upload_handle);
106}
107
108#endif // DETWS_ENABLE_UPLOAD
Single-port HTTP server with deterministic, zero-allocation execution.
Definition dwserver.h:348
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
Definition dwserver.cpp:759
Layer 7 (Application) - public HTTP routing API.
@ HTTP_POST
Non-idempotent create / action.
Standalone HTTP/1.1 request parser - no transport dependency.
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
Fully-parsed HTTP/1.1 request.
char method[DETWS_METHOD_BUF_SIZE]
HTTP method, null-terminated (OPTIONS, or WebDAV methods when enabled).
char path[MAX_PATH_LEN]
URL path, null-terminated; no query string.
Streaming file upload to an Arduino FS (DETWS_ENABLE_UPLOAD).