Skip to content

Commit 361b4e0

Browse files
Allow uploading huge files to WebServer (#2180)
From espressif/arduino-esp32#9440
1 parent fa2bfdc commit 361b4e0

File tree

7 files changed

+177
-4
lines changed

7 files changed

+177
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Upload Huge File To Filesystem Over HTTP
2+
3+
This project is an example of an HTTP server designed to facilitate the transfer of large files using the PUT method, in accordance with RFC specifications.
4+
5+
### Example cURL Command
6+
7+
```bash
8+
curl -X PUT -T ./my-file.mp3 http://pico-ip/upload/my-file.mp3
9+
```
10+
11+
## Resources
12+
13+
- RFC HTTP/1.0 - Additional Request Methods - PUT : [Link](https://datatracker.ietf.org/doc/html/rfc1945#appendix-D.1.1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#include <WiFi.h>
2+
#include <WiFiClient.h>
3+
#include <WebServer.h>
4+
#include <uri/UriRegex.h>
5+
#include <LittleFS.h>
6+
7+
#ifndef STASSID
8+
#define STASSID "your-ssid"
9+
#define STAPSK "your-password"
10+
#endif
11+
12+
const char *ssid = STASSID;
13+
const char *password = STAPSK;
14+
15+
WebServer server(80);
16+
17+
File rawFile;
18+
void handleCreate() {
19+
server.send(200, "text/plain", "");
20+
}
21+
void handleCreateProcess() {
22+
String path = "/" + server.pathArg(0);
23+
HTTPRaw& raw = server.raw();
24+
if (raw.status == RAW_START) {
25+
if (LittleFS.exists((char *)path.c_str())) {
26+
LittleFS.remove((char *)path.c_str());
27+
}
28+
rawFile = LittleFS.open(path.c_str(), "w");
29+
Serial.print("Upload: START, filename: ");
30+
Serial.println(path);
31+
} else if (raw.status == RAW_WRITE) {
32+
if (rawFile) {
33+
rawFile.write(raw.buf, raw.currentSize);
34+
}
35+
Serial.print("Upload: WRITE, Bytes: ");
36+
Serial.println(raw.currentSize);
37+
} else if (raw.status == RAW_END) {
38+
if (rawFile) {
39+
rawFile.close();
40+
}
41+
Serial.print("Upload: END, Size: ");
42+
Serial.println(raw.totalSize);
43+
}
44+
}
45+
46+
void returnFail(String msg) {
47+
server.send(500, "text/plain", msg + "\r\n");
48+
}
49+
50+
void handleNotFound() {
51+
String message = "File Not Found\n\n";
52+
message += "URI: ";
53+
message += server.uri();
54+
message += "\nMethod: ";
55+
message += (server.method() == HTTP_GET) ? "GET" : "POST";
56+
message += "\nArguments: ";
57+
message += server.args();
58+
message += "\n";
59+
for (uint8_t i = 0; i < server.args(); i++) {
60+
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
61+
}
62+
server.send(404, "text/plain", message);
63+
}
64+
65+
void setup(void) {
66+
Serial.begin(115200);
67+
68+
LittleFS.begin();
69+
70+
WiFi.mode(WIFI_STA);
71+
WiFi.begin(ssid, password);
72+
73+
while (WiFi.status() != WL_CONNECTED) {
74+
delay(500);
75+
Serial.print(".");
76+
}
77+
Serial.print("Connected to ");
78+
Serial.println(ssid);
79+
Serial.print("IP address: ");
80+
Serial.println(WiFi.localIP());
81+
82+
server.on(UriRegex("/upload/(.*)"), HTTP_PUT, handleCreate, handleCreateProcess);
83+
server.onNotFound(handleNotFound);
84+
server.begin();
85+
Serial.println("HTTP server started");
86+
87+
}
88+
89+
void loop(void) {
90+
server.handleClient();
91+
delay(2);//allow the cpu to switch to other tasks
92+
}

libraries/WebServer/src/HTTPServer.h

+19
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
3232
UPLOAD_FILE_ABORTED
3333
};
34+
enum HTTPRawStatus { RAW_START, RAW_WRITE, RAW_END, RAW_ABORTED };
3435
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
3536
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
3637

@@ -40,6 +41,10 @@ enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
4041
#define HTTP_UPLOAD_BUFLEN 1436
4142
#endif
4243

44+
#ifndef HTTP_RAW_BUFLEN
45+
#define HTTP_RAW_BUFLEN 1436
46+
#endif
47+
4348
#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request
4449
#define HTTP_MAX_DATA_AVAILABLE_WAIT 30 //ms to wait for the client to send the request when there is another client with data available
4550
#define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive
@@ -63,6 +68,16 @@ typedef struct {
6368
uint8_t buf[HTTP_UPLOAD_BUFLEN];
6469
} HTTPUpload;
6570

71+
72+
typedef struct {
73+
HTTPRawStatus status;
74+
size_t totalSize; // content size
75+
size_t currentSize; // size of data currently in buf
76+
uint8_t buf[HTTP_UPLOAD_BUFLEN];
77+
void *data; // additional data
78+
} HTTPRaw;
79+
80+
6681
#include "detail/RequestHandler.h"
6782

6883
namespace fs {
@@ -97,6 +112,9 @@ class HTTPServer {
97112
HTTPUpload& upload() {
98113
return *_currentUpload;
99114
}
115+
HTTPRaw& raw() {
116+
return *_currentRaw;
117+
}
100118

101119
String pathArg(unsigned int i); // get request path argument by number
102120
String arg(String name); // get request argument value by name
@@ -257,6 +275,7 @@ class HTTPServer {
257275
RequestArgument* _postArgs;
258276

259277
std::unique_ptr<HTTPUpload> _currentUpload;
278+
std::unique_ptr<HTTPRaw> _currentRaw;
260279

261280
int _headerKeysCount;
262281
RequestArgument* _currentHeaders;

libraries/WebServer/src/Parsing.cpp

+27-4
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,30 @@ HTTPServer::ClientFuture HTTPServer::_parseRequest(WiFiClient* client) {
188188
}
189189
}
190190

191-
if (!isForm) {
191+
if (!isForm && _currentHandler && _currentHandler->canRaw(_currentUri)) {
192+
log_v("Parse raw");
193+
_currentRaw.reset(new HTTPRaw());
194+
_currentRaw->status = RAW_START;
195+
_currentRaw->totalSize = 0;
196+
_currentRaw->currentSize = 0;
197+
log_v("Start Raw");
198+
_currentHandler->raw(*this, _currentUri, *_currentRaw);
199+
_currentRaw->status = RAW_WRITE;
200+
201+
while (_currentRaw->totalSize < (size_t)_clientContentLength) {
202+
_currentRaw->currentSize = client->readBytes(_currentRaw->buf, HTTP_RAW_BUFLEN);
203+
_currentRaw->totalSize += _currentRaw->currentSize;
204+
if (_currentRaw->currentSize == 0) {
205+
_currentRaw->status = RAW_ABORTED;
206+
_currentHandler->raw(*this, _currentUri, *_currentRaw);
207+
return CLIENT_MUST_STOP;
208+
}
209+
_currentHandler->raw(*this, _currentUri, *_currentRaw);
210+
}
211+
_currentRaw->status = RAW_END;
212+
_currentHandler->raw(*this, _currentUri, *_currentRaw);
213+
log_v("Finish Raw");
214+
} else if (!isForm) {
192215
size_t plainLength;
193216
char* plainBuf = readBytesWithTimeout(client, _clientContentLength, plainLength, HTTP_MAX_POST_WAIT);
194217
if ((int)plainLength < (int)_clientContentLength) {
@@ -333,7 +356,7 @@ void HTTPServer::_uploadWriteByte(uint8_t b) {
333356
_currentUpload->buf[_currentUpload->currentSize++] = b;
334357
}
335358

336-
int HTTPServer::_uploadReadByte(WiFiClient* client) {
359+
int HTTPServer::_uploadReadByte(WiFiClient * client) {
337360
int res = client->read();
338361
if (res < 0) {
339362
// keep trying until you either read a valid byte or timeout
@@ -376,7 +399,7 @@ int HTTPServer::_uploadReadByte(WiFiClient* client) {
376399
return res;
377400
}
378401

379-
bool HTTPServer::_parseForm(WiFiClient* client, String boundary, uint32_t len) {
402+
bool HTTPServer::_parseForm(WiFiClient * client, String boundary, uint32_t len) {
380403
(void) len;
381404
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
382405
String line;
@@ -595,7 +618,7 @@ bool HTTPServer::_parseForm(WiFiClient* client, String boundary, uint32_t len) {
595618
return false;
596619
}
597620

598-
String HTTPServer::urlDecode(const String& text) {
621+
String HTTPServer::urlDecode(const String & text) {
599622
String decoded = "";
600623
char temp[] = "0x00";
601624
unsigned int len = text.length();

libraries/WebServer/src/WebServerTemplate.h

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ void WebServerTemplate<ServerType, DefaultPort>::handleClient() {
171171
}
172172
_currentStatus = HC_NONE;
173173
_currentUpload.reset();
174+
_currentRaw.reset();
174175
}
175176

176177
if (callYield) {

libraries/WebServer/src/detail/RequestHandler.h

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ class RequestHandler {
1515
(void) uri;
1616
return false;
1717
}
18+
virtual bool canRaw(String uri) {
19+
(void) uri;
20+
return false;
21+
}
1822
virtual bool handle(HTTPServer& server, HTTPMethod requestMethod, String requestUri) {
1923
(void) server;
2024
(void) requestMethod;
@@ -26,6 +30,11 @@ class RequestHandler {
2630
(void) requestUri;
2731
(void) upload;
2832
}
33+
virtual void raw(HTTPServer& server, String requestUri, HTTPRaw& raw) {
34+
(void) server;
35+
(void) requestUri;
36+
(void) raw;
37+
}
2938

3039
RequestHandler* next() {
3140
return _next;

libraries/WebServer/src/detail/RequestHandlersImpl.h

+16
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ class FunctionRequestHandler : public RequestHandler {
4242

4343
return true;
4444
}
45+
bool canRaw(String requestUri) override {
46+
(void) requestUri;
47+
if (!_ufn || _method == HTTP_GET) {
48+
return false;
49+
}
50+
51+
return true;
52+
}
4553

4654
bool handle(HTTPServer& server, HTTPMethod requestMethod, String requestUri) override {
4755
(void) server;
@@ -61,6 +69,14 @@ class FunctionRequestHandler : public RequestHandler {
6169
}
6270
}
6371

72+
void raw(HTTPServer& server, String requestUri, HTTPRaw& raw) override {
73+
(void)server;
74+
(void)raw;
75+
if (canRaw(requestUri)) {
76+
_ufn();
77+
}
78+
}
79+
6480
protected:
6581
HTTPServer::THandlerFunction _fn;
6682
HTTPServer::THandlerFunction _ufn;

0 commit comments

Comments
 (0)