Open zekageri opened 1 year ago
I'm thinking about something like this
#include <LittleFS.h>
auto _Response::sendFile(const char* filePath) -> void {
if( !LittleFS.exists(filePath) ){ return; }
fs::File file = LittleFS.open(filePath, "r");
if(!file){ return; }
while(file.available()){
// read file to buffer for sending or send it byte by byte?
}
file.close();
}
Or should we just implement it in user side? I created a quick draft, something like this:
#include <LittleFS.h>
char buffer[5000];
class index {
public:
static constexpr char *filename = "/index.html";
static const char *content() {
int size = fileSys.readFile("/index.html",buffer,5000);
Serial.printf("Read file size: %d\n", size);
Serial.println(buffer);
return buffer;
}
};
app.get("/", [](request &req, response &res,const NextCallback next) {
express_::File file{"index.html", index::content};
res.sendFile(file);
});
It's an ugly code but works.
Or something like this
const int expressFileSize = 5000; // should be dinamyc depending on the file size.
char expressFileBuffer[expressFileSize];
char* expressFilePath;
const char *expressFileCB() {
int size = fileSys.getFileSize(expressFilePath);
if( !size ){
fileSys.readFile("/notFound.html",expressFileBuffer,expressFileSize);
}else{
fileSys.readFile(expressFilePath,expressFileBuffer,expressFileSize);
}
return expressFileBuffer;
}
express_::File createExpressFile( const char* path ){
expressFilePath = (char*)path;
express_::File file{path, expressFileCB};
return file;
}
app.get("/", [](request &req, response &res,const NextCallback next) {
express_::File file = createExpressFile("/index.html");
res.sendFile(file);
});
See #8, what file system to use? (use seem to use LittleFS) - or how to choose one at compile time (eg through the use of C++ templates)
Offical new file system for esp32 is LittleFS but it could accept a standard File as an input. Look at Me_No_Dev's ESPAsyncWebServer implementation
AsyncWebserver does this:
AsyncWebServerResponse *response = request->beginResponse(
LittleFS,
MAIN_PATH,
"text/html"
);
//response->addHeader("Set-Cookie", cookie);
//response->addHeader("Content-Encoding", "gzip");
request->send(response);
You can pass in the file system.
ESPAsyncWebServer.h
AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
WebRequest.cpp
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
if(fs.exists(path) || (!download && fs.exists(path+".gz")))
return new AsyncFileResponse(fs, path, contentType, download, callback);
return NULL;
}
WebResponses.cpp
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
_code = 200;
_path = path;
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
_path = _path+".gz";
addHeader("Content-Encoding", "gzip");
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;
}
_content = fs.open(_path, "r");
_contentLength = _content.size();
if(contentType == "")
_setContentType(path);
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
// set filename and force download
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
} else {
// set filename and force rendering
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
}
addHeader("Content-Disposition", buf);
}
So it's just pushes the file system too.
I'm thiking about something like this:
/**
* @brief Send the contents of a file from the file system as the response.
*
* @param filePath The path of the file to send.
*/
auto _Response::sendFile(FS &fs, const char *filePath) -> void {
// Open the file from LittleFS
fs::File file = fs.open(filePath);
// Check if the file was successfully opened
if (file) {
// Get the file size
size_t fileSize = file.size();
// Allocate a buffer for the file contents
char *buffer = new char[fileSize + 1];
// Read the file contents into the buffer
file.readBytes(buffer, fileSize);
// Close the file
file.close();
// Set the response headers
this->set(F("Content-Length"), String(fileSize));
this->status(HttpStatus::OK);
// Send the file contents as the response body
this->send(buffer);
// Free the buffer
delete[] buffer;
} else {
// Failed to open the file, set appropriate response status
this->status(HttpStatus::NOT_FOUND);
}
}
We could use it like this
app.get("/", [](request &req, response &res,const NextCallback next) {
res.sendFile(LittleFS,"/index.html");
});
/**
* @brief Send the contents of a file from the file system as the response.
*
* @param filePath The path of the file to send.
*/
auto _Response::sendFile(FS &fs, const char *filePath) -> void {
fs::File file = fs.open(filePath);
if (file) {
size_t fileSize = file.size();
// Allocate a buffer with file size + 1
char *buffer = new char[fileSize + 1];
// Read the file contents directly into the buffer
file.readBytes(buffer, fileSize);
// Close the file
file.close();
// Set the response headers
this->set(ContentType, F("text/html"));
this->set(ContentLength, String(fileSize));
status(HttpStatus::OK);
body_ = buffer;
delete[] buffer;
} else {
status(HttpStatus::NOT_FOUND);
}
}
This way i can send a file from the file system but it is a little funky looking. Maybe because a character encoding header is missing? I will check this out
So i forgot to add a null terminator. It works like this now
/**
* @brief Send the contents of a file from the file system as the response.
*
* @param filePath The path of the file to send.
*/
auto _Response::sendFile(FS &fs, const char *filePath) -> void {
fs::File file = fs.open(filePath);
if (file) {
size_t fileSize = file.size();
// Allocate a buffer with file size + 1
char *buffer = new char[fileSize + 1];
// Read the file contents directly into the buffer
file.readBytes(buffer, fileSize);
// Close the file
file.close();
// Set the response headers
this->set(ContentType, mimeType.getType(filePath)); // I have added a library to automatically handle mime types
this->set(ContentLength, String(fileSize+1));
status(HttpStatus::OK);
// Add null terminator at the end of the buffer
buffer[fileSize] = '\0';
body_ = buffer;
delete[] buffer;
} else {
status(HttpStatus::NOT_FOUND);
}
}
Yeah, so it doesn't handle large files well. We have to send it chunked from the file system no matter what.
The library has been designed not to copy the input buffer (but use pointers over the input buffer) - not sure what is going on, but it could be that at the data pointer from the FS is not behaving well.
Context: I try to avoid new/delete to avoid memory fragmentation on the microcontroller.
Yeah it would be cool if we could pull chunks directly from the file without copying all its content to a buffer
??