Description
Describe the bug
When a client aborts a request, the program hangs.
The sketch below has been adapted from the HTTP forms example.
Please use the sketch from the second post, it's simpler!
More complex sketch with multipart/form-data
/**
* Example for the ESP32 HTTP(S) Webserver
*
* IMPORTANT NOTE:
* To run this script, you need to
* 1) Enter your WiFi SSID and PSK below this comment
* 2) Make sure to have certificate data available. You will find a
* shell script and instructions to do so in the library folder
* under extras/
*
* This script will install an HTTPS Server on your ESP32 with the following
* functionalities:
* - Show simple page on web server root that includes some HTML Forms
* - Define a POST handler that handles the forms using the HTTPBodyParser API
* provided by the library.
* - 404 for everything else
*/
// TODO: Configure your WiFi here
#define WIFI_SSID "<insert your WiFi>"
#define WIFI_PSK "<insert your WiFi pass>"
// Include certificate data (see note above)
// We will use wifi
#include <WiFi.h>
// We will use SPIFFS and FS
#include <SPIFFS.h>
#include <FS.h>
// Includes for the server
#include <HTTPSServer.hpp>
#include <SSLCert.hpp>
#include <HTTPRequest.hpp>
#include <HTTPResponse.hpp>
#include <HTTPBodyParser.hpp>
#include <HTTPMultipartBodyParser.hpp>
#include <HTTPURLEncodedBodyParser.hpp>
// The HTTPS Server comes in a separate namespace. For easier use, include it here.
using namespace httpsserver;
// Create an SSL certificate object from the files included above
// SSLCert cert = SSLCert(
// example_crt_DER, example_crt_DER_len,
// example_key_DER, example_key_DER_len
// );
// Create an SSL-enabled server that uses the certificate
// The contstructor takes some more parameters, but we go for default values here.
SSLCert * cert;
HTTPSServer * secureServer;
// Declare some handler functions for the various URLs on the server
// See the static-page example for how handler functions work.
// The comments in setup() describe what each handler function does in this example.
void handleFormUpload(HTTPRequest * req, HTTPResponse * res);
void handleFormUpload(HTTPRequest * req, HTTPResponse * res) {
// First, we need to check the encoding of the form that we have received.
// The browser will set the Content-Type request header, so we can use it for that purpose.
// Then we select the body parser based on the encoding.
// Actually we do this only for documentary purposes, we know the form is going
// to be multipart/form-data.
HTTPBodyParser *parser;
std::string contentType = req->getHeader("Content-Type");
// The content type may have additional properties after a semicolon, for exampel:
// Content-Type: text/html;charset=utf-8
// Content-Type: multipart/form-data;boundary=------s0m3w31rdch4r4c73rs
// As we're interested only in the actual mime _type_, we strip everything after the
// first semicolon, if one exists:
size_t semicolonPos = contentType.find(";");
if (semicolonPos != std::string::npos) {
contentType = contentType.substr(0, semicolonPos);
}
// Now, we can decide based on the content type:
if (contentType == "multipart/form-data") {
parser = new HTTPMultipartBodyParser(req);
} else {
Serial.printf("Unknown POST Content-Type: %s\n", contentType.c_str());
return;
}
res->println("<html><head><title>File Upload</title></head><body><h1>File Upload</h1>");
// We iterate over the fields. Any field with a filename is uploaded.
// Note that the BodyParser consumes the request body, meaning that you can iterate over the request's
// fields only a single time. The reason for this is that it allows you to handle large requests
// which would not fit into memory.
// parser->nextField() will move the parser to the next field in the request body (field meaning a
// form field, if you take the HTML perspective). After the last field has been processed, nextField()
// returns false and the while loop ends.
while(parser->nextField()) {
// For Multipart data, each field has three properties:
// The name ("name" value of the <input> tag)
// The filename (If it was a <input type="file">, this is the filename on the machine of the
// user uploading it)
// The mime type (It is determined by the client. So do not trust this value and blindly start
// parsing files only if the type matches)
std::string name = parser->getFieldName();
std::string filename = parser->getFieldFilename();
std::string mimeType = parser->getFieldMimeType();
// We log all three values, so that you can observe the upload on the serial monitor:
Serial.printf("handleFormUpload: field name='%s', filename='%s', mimetype='%s'\n", name.c_str(), filename.c_str(), mimeType.c_str());
// With endOfField you can check whether the end of field has been reached or if there's
// still data pending. With multipart bodies, you cannot know the field size in advance.
size_t fileLength = 0;
while (!parser->endOfField()) {
byte buf[512];
size_t readLength = parser->read(buf, 512);
fileLength += readLength;
// Do nothing with the buffer, to keep things simple
}
}
res->println("</body></html>");
delete parser;
}
void setup() {
// For logging
Serial.begin(115200);
// Connect to WiFi
Serial.println("Setting up WiFi");
WiFi.begin(WIFI_SSID, WIFI_PSK);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.print("Connected. IP=");
Serial.println(WiFi.localIP());
// Setup filesystem
if (!SPIFFS.begin(true)) Serial.println("Mounting SPIFFS failed");
cert = new SSLCert();
int createCertResult = createSelfSignedCert(
*cert,
KEYSIZE_1024,
"CN=myesp32.local,O=FancyCompany,C=DE",
"20190101000000",
"20300101000000"
);
if (createCertResult != 0) {
Serial.printf("Cerating certificate failed. Error Code = 0x%02X, check SSLCert.hpp for details", createCertResult);
while(true) delay(500);
}
secureServer = new HTTPSServer(cert);
// For every resource available on the server, we need to create a ResourceNode
// The ResourceNode links URL and HTTP method to a handler function
// The handleFormUpload handler handles the file upload from the root node. As the form
// is submitted via post, we need to specify that as handler method here:
ResourceNode * nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload);
// Add all nodes to the server so they become accessible:
secureServer->registerNode(nodeFormUpload);
Serial.println("Starting server...");
secureServer->start();
if (secureServer->isRunning()) {
Serial.println("Server ready.");
}
}
void loop() {
// This call will let the server do its work
secureServer->loop();
// Other code would go here...
delay(1);
}
Make a POST request to the endpoint using a large enough file
I used the GUI tool insomnia
for that.
You may also use curl
or any other tool.
Just make sure to use a file large enough to abort the requests while the file has not been fully transmitted.
Stop the transmission midway.
e.g. by pressing Ctrl + C or clicking on Cancel in Insomnia
Expected Behavior
The server should stop processing the request and indicate that information e.g. by a method returning a boolean.
Actual Behavior
The server either sees transmisson of zero-length data
or does not complete the req->readBytes() method.
ESP32 Module
Please provide specifications of your module
- RAM/PSRAM: 320kiB
- Flash Size: 4MiB
Software
- IDE and Version: PlatformIO 4.3.4
- OS: Windows 10
- Client used to access the server: Insomnia