ESP8266内存不足:如何分块发送大文件作为POST请求?
分块发送大文件到dpaste.com的实现方案
由于设备内存有限(仅40KB),无法一次性加载400KB文件,不能依赖HTTPClient默认的POST方法(会将整个请求体缓存到内存),需要直接通过WiFiClientSecure手动构建分块传输请求。
实现思路
- 跳过
HTTPClient的内存缓存逻辑,直接用WiFiClientSecure建立SSL连接 - 发送包含
Transfer-Encoding: chunked的请求头,告知服务器采用分块传输 - 分块读取文件内容,每次读取大小控制在设备内存余量内(比如32KB)
- 按照HTTP分块编码格式逐个发送数据块,最后发送结束标记
- 适配
application/x-www-form-urlencoded格式,对表单内容做URL编码处理
完整代码示例
#include <WiFiClientSecure.h> #include <SPIFFS.h> // 辅助函数:发送单个分块(遵循分块编码格式) void send_chunk(WiFiClientSecure *client, const char* data, size_t length) { // 发送块长度(十六进制格式) client->print(String(length, HEX)); client->print("\r\n"); // 发送块内容 client->print(data); client->print("\r\n"); } // 辅助函数:URL编码(适配表单格式要求) String urlEncode(const String& str) { String encodedString = ""; char c; char code0; char code1; for (unsigned int i = 0; i < str.length(); i++) { c = str.charAt(i); if (c == ' ') { encodedString += '+'; } else if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { encodedString += c; } else { code1 = (c & 0xf) + '0'; if ((c & 0xf) > 9) code1 = (c & 0xf) - 10 + 'A'; c = (c >> 4) & 0xf; code0 = c + '0'; if (c > 9) code0 = c - 10 + 'A'; encodedString += '%'; encodedString += code0; encodedString += code1; } } return encodedString; } // 分块发送大文件到dpaste的核心函数 void dpaste_send_large_file(WiFiClientSecure *client, String title, const char* filePath) { // 建立与dpaste服务器的SSL连接 if (!client->connect("dpaste.com", 443)) { Serial.println("[ERROR] 连接dpaste服务器失败"); return; } // 发送HTTP请求头 client->print("POST /api/ HTTP/1.1\r\n"); client->print("Host: dpaste.com\r\n"); client->print("Authorization: TOKEN\r\n"); // 替换为你的实际授权令牌 client->print("Content-Type: application/x-www-form-urlencoded\r\n"); client->print("Transfer-Encoding: chunked\r\n"); client->print("\r\n"); // 头信息结束标记 // 打开要发送的文件(示例用SPIFFS,若用SD卡可替换为SD库) File file = SPIFFS.open(filePath, "r"); if (!file) { Serial.println("[ERROR] 无法打开目标文件"); client->stop(); return; } // 发送表单前缀:content= String formPrefix = "content="; send_chunk(client, formPrefix.c_str(), formPrefix.length()); // 分块读取并发送文件内容 const size_t chunkSize = 32768; // 32KB,小于设备40KB内存预留余量 char buffer[chunkSize]; size_t bytesRead; while ((bytesRead = file.readBytes(buffer, chunkSize)) > 0) { // 对当前文件块进行URL编码(若文件无特殊字符可跳过此步节省资源) String encodedChunk = urlEncode(String(buffer, bytesRead)); send_chunk(client, encodedChunk.c_str(), encodedChunk.length()); } file.close(); // 发送表单后缀:&title=XXX&expiry_days=30 String formSuffix = "&title=" + urlEncode(title) + "&expiry_days=30"; send_chunk(client, formSuffix.c_str(), formSuffix.length()); // 发送分块结束标记 client->print("0\r\n\r\n"); // 读取并打印服务器响应 Serial.println("[DEBUG] 服务器响应:"); while (client->connected() || client->available()) { if (client->available()) { String line = client->readStringUntil('\n'); Serial.println(line); } } // 关闭连接 client->stop(); } // 在setup中初始化SPIFFS(示例) void setup() { Serial.begin(115200); // 初始化SPIFFS(若首次使用需格式化) if (!SPIFFS.begin(true)) { Serial.println("[ERROR] SPIFFS初始化失败"); return; } // 此处添加WiFi连接代码... }
关键注意事项
- 内存控制:
chunkSize设置为32KB,确保单块数据占用内存不超过设备的40KB限制,避免内存溢出 - URL编码:如果文件内容包含
&、=、%等特殊字符,必须编码,否则会破坏表单结构;纯文本无特殊字符可跳过编码 - 令牌替换:将代码中的
TOKEN替换为你的dpaste授权令牌 - 文件系统:示例使用SPIFFS存储文件,若使用SD卡,需替换为SD库的文件操作逻辑
- 分块格式:严格遵循HTTP分块编码规则,每个块的长度必须是十六进制,末尾必须发送
0\r\n\r\n表示传输结束
内容的提问来源于stack exchange,提问作者Valentino Zaffrani
相关产品推荐
相关产品推荐

