通过http在Arduino ESP32上文件上传问题的任何解决方案
Any solution for file upload problem on Arduino ESP32 via http
首先是问题
用户可以使用 ajax 从 Web 上传文件。如果文件比较大,上传需要一段时间。如果用户的连接丢失或上传过程中出现问题,文件将被损坏或清空。
我应该如何保护上传过程,以便文件在由于某种原因失败时保持不变?
我在 Arduino ESP32 上使用以下库:
我的 esp32 上有一个基本的文件上传处理程序,如下所示:
server.on("/uploading", HTTP_POST, [](AsyncWebServerRequest * request) {
}, handleFileUpload);
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
if (!filename.startsWith("/"))
filename = "/" + filename;
if (LITTLEFS.exists(filename)) {
LITTLEFS.remove(filename);
}
uploadFile = LITTLEFS.open(filename, "w");
}
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
if (final) {
uploadFile.close();
if(filename == "/myHomeProgram.json"){initProgram = true;}
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "File Uploaded;"+filename);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
这工作得很好,文件在 99% 的情况下都能正确上传,但如果失败,我会丢失文件数据,或者程序的其他部分想要打开相同的文件文件也失败了。
我是否应该写入一个临时文件,如果它成功将内容以某种方式写入目标文件?
这是客户端(JS)端的示例:
// Example call:
saveFile(JSON.stringify(places),"/myHomeProgram.json","application/json");
function saveFile(data, filename, type) {
var file = new Blob([data], {type: type});
form = new FormData();
form.append("blob", file, filename);
$.ajax({
url: '/uploading',
type: 'POST',
data: form,
processData: false,
contentType: false
}).done(function(resp){
var response = resp.split(";");
$(".saveIconGraph").removeClass("fas fa-spinner fa-spin");
$(".saveIconGraph").addClass("far fa-save");
if(response[1] == "/myHomeProgram.json"){
toast("success","saveOk","progInfo",3500);
showSaved();
setTimeout(() => {
$("#saveMe").fadeOut( "slow", function() {
showSave();
});
}, 1000);
initPlaces();
}
}).fail(function(resp){
var response = resp.split(";");
$(".saveIconGraph").removeClass("fas fa-spinner fa-spin");
$(".saveIconGraph").addClass("far fa-save");
if(response[1] == "/myHomeProgram.json"){
toast("error","saveNotOk","progInfo",3500);
showSaveError();
$("#saveMeBtn").addClass("shakeEffect");
setTimeout(() => {
$("#saveMeBtn").removeClass("shakeEffect");
showSave();
}, 4500);
}
});
}
我可以在写入前将文件保存在临时 char 变量中,最后我可以匹配文件的大小和临时变量的大小,如果不相同,则回滚到上一个。这可以管理吗?
像这样:
String uploadTemp = "";
inline boolean saveFileToTemp(String fileName){
uploadTemp = "";
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
for (int i = 0; i < f.size(); i++){
uploadTemp += (char)f.read();
}
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(uploadTemp);
}
g.close();
return true;
}
inline boolean matchFileSizes(String fileName,boolean isFileExists){
boolean isCorrect = false;
if(isFileExists){
File writedFile = LITTLEFS.open(fileName, "w");
if( writedFile.size() == uploadTemp.length()){
isCorrect = true;
}else{
isCorrect = false;
}
writedFile.close();
return isCorrect;
}else{
return true;
}
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
String webResponse;
boolean error = false,isFileExists = false;
if (!index) {
if (!filename.startsWith("/"))
filename = "/" + filename;
if (LITTLEFS.exists(filename)) {
isFileExists = true;
// Save the file to a temporary String if it success we continue.
if( saveFileToTemp(filename) ){
LITTLEFS.remove(filename);
}else{
// If the file save was fail we abort everything.
webResponse = "File NOT Uploaded " + filename;
final = true;
error = true;
}
}
if( !error ){
uploadFile = LITTLEFS.open(filename, "w");
}
}
if( !error ){
// Copy content to the actual file
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
if (final) {
uploadFile.close();
if( !error ){
if( matchFileSizes(filename,isFileExists) ){
if(filename == "/myHomeProgram.json"){initProgram = true;}
webResponse = "File Uploaded " + filename;
}else{
error = true;
webResponse = "File length mismatch";
}
}
if( error ){
revertBackFile(filename);
}
Serial.println(webResponse);
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
看来问题解决了
我已经设法用外部存储器中的字符替换了字符串缓冲区。它看起来稳定但需要更多测试。我会 post 解决方案,但如果有人有更好的方法,请随时在这里评论 。
谢谢。
char * uploadTemp;
inline boolean saveFileToTemp(String fileName){
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
size_t fileSize = f.size();
uploadTemp = (char*)ps_malloc(fileSize + 1);
for (int i = 0; i < fileSize; i++){
uploadTemp[i] = (char)f.read();
}
uploadTemp[fileSize] = '[=10=]';
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(uploadTemp);
}
g.close();
return true;
}
inline boolean matchFileSizes(String fileName,boolean isFileExists){
boolean isCorrect = false;
if(isFileExists){
File writedFile = LITTLEFS.open(fileName, "w");
if( writedFile.size() == sizeof(uploadTemp)){
isCorrect = true;
}else{
isCorrect = false;
}
writedFile.close();
return isCorrect;
}else{
return true;
}
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
boolean isFileExists = false,error = false;
String webResponse = "";
int httpStatus = 200;
// Start of the file upload
if (!index) {
// Make sure that there is a / char at the start of the string
if (!filename.startsWith("/")){ filename = "/" + filename; }
// Check if the file exists
if (LITTLEFS.exists(filename)) {
isFileExists = true;
// Get the file contents for safety reasons
// If it succeded we can create a new file in the palce
if( saveFileToTemp(filename) ){
uploadFile = LITTLEFS.open(filename, "w");
}else{
// If we can not save it abort the upload process.
webResponse = "File NOT Uploaded " + filename;
final = true;error = true;
}
}
}
// If we have no error at this point, we can start to copy the content to the file.
if( !error ){
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
// If no more data we can start responding back to the client
if (final) {
uploadFile.close();
// Check if we got any error before.
if( !error && matchFileSizes(filename,isFileExists) ){
// Copyed file is the same, upload success.
if(filename == "/myHomeProgram.json"){initProgram = true;}
webResponse = "File Uploaded " + filename;
}else{
webResponse = "File length mismatch";
revertBackFile(filename);
httpStatus = 500;
}
free(uploadTemp);
AsyncWebServerResponse *response = request->beginResponse(httpStatus, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
编辑:
是啊,完全错了
我必须做以下事情:
将我们要上传的文件保存到临时字符数组中。
上传时将上传的文件变成临时文件。
如果一切顺利,将临时文件的内容复制到目标文件中。
如果失败,将保存的文件还原为原始文件并报告错误。
类似这样的东西(还在测试中):
char * prevFileTemp;
inline boolean saveFileToTemp(String fileName){
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
size_t fileSize = f.size();
prevFileTemp = (char*)ps_malloc(fileSize + 1);
for (int i = 0; i < fileSize; i++){
prevFileTemp[i] = (char)f.read();
}
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
if (LITTLEFS.exists(fileName)) {
Serial.println("Reverting back the file");
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(prevFileTemp);
}
g.close();
}
return true;
}
static const inline boolean copyContent(String fileName){
File arrivedFile = LITTLEFS.open(uploadTemp, "r");
File newFile = LITTLEFS.open(fileName, "w");
// Check if we can open the files as intended.
if( !arrivedFile || !newFile){
revertBackFile(fileName);
return false;
}
// Copy one file content to another.
for (size_t i = 0; i < arrivedFile.size(); i++) { newFile.write( (char)arrivedFile.read() ); }
// Check the sizes, if no match, abort mission.
if( newFile.size() != arrivedFile.size()){ return false; }
arrivedFile.close();newFile.close();
return true;
}
boolean isFileExists = false,uploadError = false,newFileArrived = false;
String webResponse = "",newArrivalFileName = "";
int httpStatus = 200;
inline void resetVariables(){
isFileExists = false;
uploadError = false;
webResponse = "";
httpStatus = 200;
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
// Start file upload process
if (!index) {
// Reset all the variables
resetVariables();
// Make sure that there is a '/' char at the start of the string
if (!filename.startsWith("/")){ filename = "/" + filename; }
// Open the temporary file for content copy if it is exist
if (LITTLEFS.exists(filename)) {
if( saveFileToTemp(filename) ){
uploadFile = LITTLEFS.open(uploadTemp, "w");
}else{
// If we can not save it abort the upload process.
webResponse = "File NOT Uploaded " + filename;
final = true;uploadError = true;
}
}
}
// If we have no error at this point, we can start to copy the content to the temporary file.
if( !uploadError ){
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
// If no more data we can start responding back to the client
if (final) {
if (!filename.startsWith("/")){ filename = "/" + filename; }
uploadFile.close();
if( !uploadError && copyContent(filename) ){
webResponse = "File Uploaded " + filename;
}else{
webResponse = "File length mismatch";
revertBackFile(filename);
httpStatus = 500;
}
free(prevFileTemp);
AsyncWebServerResponse *response = request->beginResponse(httpStatus, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
首先是问题
用户可以使用 ajax 从 Web 上传文件。如果文件比较大,上传需要一段时间。如果用户的连接丢失或上传过程中出现问题,文件将被损坏或清空。
我应该如何保护上传过程,以便文件在由于某种原因失败时保持不变?
我在 Arduino ESP32 上使用以下库:
我的 esp32 上有一个基本的文件上传处理程序,如下所示:
server.on("/uploading", HTTP_POST, [](AsyncWebServerRequest * request) {
}, handleFileUpload);
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
if (!filename.startsWith("/"))
filename = "/" + filename;
if (LITTLEFS.exists(filename)) {
LITTLEFS.remove(filename);
}
uploadFile = LITTLEFS.open(filename, "w");
}
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
if (final) {
uploadFile.close();
if(filename == "/myHomeProgram.json"){initProgram = true;}
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "File Uploaded;"+filename);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
这工作得很好,文件在 99% 的情况下都能正确上传,但如果失败,我会丢失文件数据,或者程序的其他部分想要打开相同的文件文件也失败了。 我是否应该写入一个临时文件,如果它成功将内容以某种方式写入目标文件? 这是客户端(JS)端的示例:
// Example call:
saveFile(JSON.stringify(places),"/myHomeProgram.json","application/json");
function saveFile(data, filename, type) {
var file = new Blob([data], {type: type});
form = new FormData();
form.append("blob", file, filename);
$.ajax({
url: '/uploading',
type: 'POST',
data: form,
processData: false,
contentType: false
}).done(function(resp){
var response = resp.split(";");
$(".saveIconGraph").removeClass("fas fa-spinner fa-spin");
$(".saveIconGraph").addClass("far fa-save");
if(response[1] == "/myHomeProgram.json"){
toast("success","saveOk","progInfo",3500);
showSaved();
setTimeout(() => {
$("#saveMe").fadeOut( "slow", function() {
showSave();
});
}, 1000);
initPlaces();
}
}).fail(function(resp){
var response = resp.split(";");
$(".saveIconGraph").removeClass("fas fa-spinner fa-spin");
$(".saveIconGraph").addClass("far fa-save");
if(response[1] == "/myHomeProgram.json"){
toast("error","saveNotOk","progInfo",3500);
showSaveError();
$("#saveMeBtn").addClass("shakeEffect");
setTimeout(() => {
$("#saveMeBtn").removeClass("shakeEffect");
showSave();
}, 4500);
}
});
}
我可以在写入前将文件保存在临时 char 变量中,最后我可以匹配文件的大小和临时变量的大小,如果不相同,则回滚到上一个。这可以管理吗?
像这样:
String uploadTemp = "";
inline boolean saveFileToTemp(String fileName){
uploadTemp = "";
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
for (int i = 0; i < f.size(); i++){
uploadTemp += (char)f.read();
}
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(uploadTemp);
}
g.close();
return true;
}
inline boolean matchFileSizes(String fileName,boolean isFileExists){
boolean isCorrect = false;
if(isFileExists){
File writedFile = LITTLEFS.open(fileName, "w");
if( writedFile.size() == uploadTemp.length()){
isCorrect = true;
}else{
isCorrect = false;
}
writedFile.close();
return isCorrect;
}else{
return true;
}
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
String webResponse;
boolean error = false,isFileExists = false;
if (!index) {
if (!filename.startsWith("/"))
filename = "/" + filename;
if (LITTLEFS.exists(filename)) {
isFileExists = true;
// Save the file to a temporary String if it success we continue.
if( saveFileToTemp(filename) ){
LITTLEFS.remove(filename);
}else{
// If the file save was fail we abort everything.
webResponse = "File NOT Uploaded " + filename;
final = true;
error = true;
}
}
if( !error ){
uploadFile = LITTLEFS.open(filename, "w");
}
}
if( !error ){
// Copy content to the actual file
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
if (final) {
uploadFile.close();
if( !error ){
if( matchFileSizes(filename,isFileExists) ){
if(filename == "/myHomeProgram.json"){initProgram = true;}
webResponse = "File Uploaded " + filename;
}else{
error = true;
webResponse = "File length mismatch";
}
}
if( error ){
revertBackFile(filename);
}
Serial.println(webResponse);
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
看来问题解决了
我已经设法用外部存储器中的字符替换了字符串缓冲区。它看起来稳定但需要更多测试。我会 post 解决方案,但如果有人有更好的方法,请随时在这里评论 。
谢谢。
char * uploadTemp;
inline boolean saveFileToTemp(String fileName){
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
size_t fileSize = f.size();
uploadTemp = (char*)ps_malloc(fileSize + 1);
for (int i = 0; i < fileSize; i++){
uploadTemp[i] = (char)f.read();
}
uploadTemp[fileSize] = '[=10=]';
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(uploadTemp);
}
g.close();
return true;
}
inline boolean matchFileSizes(String fileName,boolean isFileExists){
boolean isCorrect = false;
if(isFileExists){
File writedFile = LITTLEFS.open(fileName, "w");
if( writedFile.size() == sizeof(uploadTemp)){
isCorrect = true;
}else{
isCorrect = false;
}
writedFile.close();
return isCorrect;
}else{
return true;
}
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
boolean isFileExists = false,error = false;
String webResponse = "";
int httpStatus = 200;
// Start of the file upload
if (!index) {
// Make sure that there is a / char at the start of the string
if (!filename.startsWith("/")){ filename = "/" + filename; }
// Check if the file exists
if (LITTLEFS.exists(filename)) {
isFileExists = true;
// Get the file contents for safety reasons
// If it succeded we can create a new file in the palce
if( saveFileToTemp(filename) ){
uploadFile = LITTLEFS.open(filename, "w");
}else{
// If we can not save it abort the upload process.
webResponse = "File NOT Uploaded " + filename;
final = true;error = true;
}
}
}
// If we have no error at this point, we can start to copy the content to the file.
if( !error ){
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
// If no more data we can start responding back to the client
if (final) {
uploadFile.close();
// Check if we got any error before.
if( !error && matchFileSizes(filename,isFileExists) ){
// Copyed file is the same, upload success.
if(filename == "/myHomeProgram.json"){initProgram = true;}
webResponse = "File Uploaded " + filename;
}else{
webResponse = "File length mismatch";
revertBackFile(filename);
httpStatus = 500;
}
free(uploadTemp);
AsyncWebServerResponse *response = request->beginResponse(httpStatus, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
编辑:
是啊,完全错了
我必须做以下事情:
将我们要上传的文件保存到临时字符数组中。
上传时将上传的文件变成临时文件。
如果一切顺利,将临时文件的内容复制到目标文件中。
如果失败,将保存的文件还原为原始文件并报告错误。
类似这样的东西(还在测试中):
char * prevFileTemp;
inline boolean saveFileToTemp(String fileName){
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
size_t fileSize = f.size();
prevFileTemp = (char*)ps_malloc(fileSize + 1);
for (int i = 0; i < fileSize; i++){
prevFileTemp[i] = (char)f.read();
}
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
if (LITTLEFS.exists(fileName)) {
Serial.println("Reverting back the file");
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(prevFileTemp);
}
g.close();
}
return true;
}
static const inline boolean copyContent(String fileName){
File arrivedFile = LITTLEFS.open(uploadTemp, "r");
File newFile = LITTLEFS.open(fileName, "w");
// Check if we can open the files as intended.
if( !arrivedFile || !newFile){
revertBackFile(fileName);
return false;
}
// Copy one file content to another.
for (size_t i = 0; i < arrivedFile.size(); i++) { newFile.write( (char)arrivedFile.read() ); }
// Check the sizes, if no match, abort mission.
if( newFile.size() != arrivedFile.size()){ return false; }
arrivedFile.close();newFile.close();
return true;
}
boolean isFileExists = false,uploadError = false,newFileArrived = false;
String webResponse = "",newArrivalFileName = "";
int httpStatus = 200;
inline void resetVariables(){
isFileExists = false;
uploadError = false;
webResponse = "";
httpStatus = 200;
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
// Start file upload process
if (!index) {
// Reset all the variables
resetVariables();
// Make sure that there is a '/' char at the start of the string
if (!filename.startsWith("/")){ filename = "/" + filename; }
// Open the temporary file for content copy if it is exist
if (LITTLEFS.exists(filename)) {
if( saveFileToTemp(filename) ){
uploadFile = LITTLEFS.open(uploadTemp, "w");
}else{
// If we can not save it abort the upload process.
webResponse = "File NOT Uploaded " + filename;
final = true;uploadError = true;
}
}
}
// If we have no error at this point, we can start to copy the content to the temporary file.
if( !uploadError ){
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
// If no more data we can start responding back to the client
if (final) {
if (!filename.startsWith("/")){ filename = "/" + filename; }
uploadFile.close();
if( !uploadError && copyContent(filename) ){
webResponse = "File Uploaded " + filename;
}else{
webResponse = "File length mismatch";
revertBackFile(filename);
httpStatus = 500;
}
free(prevFileTemp);
AsyncWebServerResponse *response = request->beginResponse(httpStatus, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}