ESP32服务器大文件分片上传:JS读取报错及方案咨询
问题背景
用ESP32作为服务器搭建嵌入式项目,需要在浏览器上传大文件到ESP32。现有整文件上传方案可运行,但上传速度极慢,仅以约1.5kB的分片传输数据。希望通过JavaScript拆分文件为分片后POST到ESP32,同时在收到每个分片的200响应后更新进度条,实现上传进度反馈。
现有整文件上传方案
HTML代码
<div class = "upload"> <form method = "POST" action = "/doUpload" enctype="multipart/form-data"> <input type="file" name="data"/> <input type="submit" name="upload" value="Upload" title = "Upload Files"> </form> </div>
ESP32代码
AsyncWebServer server(80); server.on("/doUpload", HTTP_POST, [](AsyncWebServerRequest *request) {},[](AsyncWebServerRequest *request, const String filename, size_t index, uint8_t *data, size_t len, bool final) { handleUpload(request, filename, index, data, len, final); } ); void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *myData, size_t len, bool final) { ...read data... request->send(200, "text/plain", "Upload done."); }
尝试JS读取文件时的报错
测试用HTML/JS代码
<html> <head> <script src="jquery331min.js"></script> <!-- <script src="/canvasjsmin.js"></script> --> <meta charset="UTF-8"> <title>Dyno Wagen</title> </head> <body> <!--<div class = "upload">--> <div> <!--<form method = "POST" action = "/doUpload" enctype="multipart/form-data">--> <form method = "POST" action = "loadFile" enctype="multipart/form-data"> <input type="file" name="data"/> <input type="submit" name="upload" value="Upload" title = "Upload Files"> </form> </div> <div id="outputDiv"></div> <script> function loadFile() { console.log("loadFile"); var file = document.getElementByName('data'); console.log("length: " + file.files.length); if(file.files.length) { var reader = new FileReader(); reader.onload = function(e) { document.getElementById('outputDiv').innerHTML = e.target.result; }; reader.readAsBinaryString(file.files[0]); } } </script> </body> </html>
Chrome报错信息
1173575, non-JS module files deprecated. VM:143.161
解决方案
1. 报错原因分析
- JS语法错误:
document.getElementByName应为document.getElementsByName(复数形式),该方法返回元素集合,需取第一个元素:var file = document.getElementsByName('data')[0]; - 模块加载警告:Chrome对非JS模块文件的加载规则更新,给jQuery的script标签加上
type="text/javascript"即可解决:<script src="jquery331min.js" type="text/javascript"></script> - API废弃:
readAsBinaryString已被废弃,建议用slice直接截取文件分片,比FileReader更高效,适合分片上传场景。
2. 分片上传完整实现
前端HTML/JS(带进度条)
<html> <head> <script src="jquery331min.js" type="text/javascript"></script> <meta charset="UTF-8"> <title>Dyno Wagen</title> <style> .progress-bar { width: 100%; height: 20px; background-color: #eee; margin-top: 10px; border-radius: 10px; } .progress-fill { height: 100%; background-color: #4CAF50; width: 0%; border-radius: 10px; transition: width 0.3s ease; } </style> </head> <body> <div> <input type="file" id="fileInput" name="data"/> <button id="uploadBtn" onclick="startUpload()">Upload</button> </div> <div class="progress-bar"> <div class="progress-fill" id="progressFill"></div> </div> <div id="statusText"></div> <script> const CHUNK_SIZE = 1024 * 16; // 16KB分片,可根据ESP32性能调整 let currentChunk = 0; let file = null; let totalChunks = 0; function startUpload() { file = document.getElementById('fileInput').files[0]; if (!file) { alert("请先选择文件"); return; } totalChunks = Math.ceil(file.size / CHUNK_SIZE); currentChunk = 0; updateProgress(0); uploadNextChunk(); } function uploadNextChunk() { if (currentChunk >= totalChunks) { document.getElementById('statusText').textContent = "上传完成!"; return; } // 截取当前分片 const start = currentChunk * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end); // 构建表单数据,携带分片标识 const formData = new FormData(); formData.append('data', chunk); formData.append('filename', file.name); formData.append('chunkIndex', currentChunk); formData.append('totalChunks', totalChunks); // 发送分片请求 fetch('/doUpload', { method: 'POST', body: formData }) .then(response => { if (response.ok) { currentChunk++; const progress = (currentChunk / totalChunks) * 100; updateProgress(progress); document.getElementById('statusText').textContent = `已上传 ${currentChunk}/${totalChunks} 分片`; uploadNextChunk(); // 递归上传下一分片 } else { throw new Error('分片上传失败'); } }) .catch(error => { document.getElementById('statusText').textContent = `上传出错:${error.message},请重试`; }); } function updateProgress(percent) { document.getElementById('progressFill').style.width = percent + '%'; } </script> </body> </html>
ESP32后端修改(支持分片合并)
#include <SPIFFS.h> // 存储当前上传的文件状态 struct UploadInfo { File file; String filename; int totalChunks; int receivedChunks; }; UploadInfo currentUpload; void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *myData, size_t len, bool final) { // 获取分片参数 int chunkIndex = request->arg("chunkIndex").toInt(); int totalChunks = request->arg("totalChunks").toInt(); String uploadFilename = request->arg("filename"); if (!index) { // 第一个分片,初始化文件 if (currentUpload.file) { currentUpload.file.close(); } currentUpload.filename = uploadFilename; currentUpload.totalChunks = totalChunks; currentUpload.receivedChunks = 0; // 打开SPIFFS文件准备写入 currentUpload.file = SPIFFS.open("/" + uploadFilename, FILE_WRITE); if (!currentUpload.file) { request->send(500, "text/plain", "无法创建文件"); return; } } if (currentUpload.file) { // 写入当前分片数据 currentUpload.file.write(myData, len); currentUpload.receivedChunks++; if (currentUpload.receivedChunks == currentUpload.totalChunks) { // 所有分片上传完成 currentUpload.file.close(); request->send(200, "text/plain", "分片上传完成"); } else { // 分片接收成功,返回200继续下一分片 request->send(200, "text/plain", "分片接收成功"); } } else { request->send(500, "text/plain", "文件打开失败"); } } void setup() { // 初始化SPIFFS if (!SPIFFS.begin(true)) { Serial.println("SPIFFS初始化失败"); return; } // 初始化服务器 server.begin(); }
关键说明
- 分片大小调整:
CHUNK_SIZE可根据ESP32内存情况调整,建议从16KB开始测试,太大可能导致内存溢出,太小会增加请求次数。 - 多用户上传支持:当前用全局变量存储上传状态,仅支持单用户上传;若需多用户,可改用
文件名+上传ID作为键的字典来管理不同上传任务。 - 进度条实现:通过动态修改CSS宽度实现进度更新,搭配状态文本让用户清晰了解上传进度。
内容的提问来源于stack exchange,提问作者Tomáš 'Guns Blazing' Frček
相关产品推荐
相关产品推荐

