You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:59:57