WinInet上传文件必须用HttpOpenRequest+HttpSendRequest组合,因无专用上传函数,InternetWriteFile不适用于HTTP POST;需手动构造RFC 7578兼容的multipart/form-data请求体(含正确CRLF、boundary、空行),并精确传入HttpSendRequest的lpOptional参数。

WinInet 上传文件必须用 HttpOpenRequest + HttpSendRequest 组合
WinInet 没有“上传专用函数”,InternetWriteFile 不能直接用于 HTTP POST 文件上传——它只适用于已建立的连接流(如 FTP),而 HTTP 上传需构造符合 RFC 7230 的请求头和分块 body。正确路径是:HttpOpenRequest 创建请求句柄,设置 INTERNET_FLAG_NO_CACHE_WRITE 和 INTERNET_FLAG_RELOAD 避免缓存干扰,再用 HttpSendRequest 一次性提交完整请求(含 headers + body)。
常见错误是试图先调用 HttpSendRequest 发 header,再循环调用 InternetWriteFile 写 body——这在 WinInet 中不被支持,服务器会收到不完整的请求或直接断连。
- 必须把整个请求体(包括 multipart boundary、headers、file content、结尾边界)预先拼好到内存缓冲区或临时文件中
- 若文件较大(>10MB),建议用临时文件 +
lpszHeaders中指定Content-Length,避免内存峰值过高 -
HttpSendRequest的lpOptional参数即指向这个完整请求体,dwOptionalLength必须精确(多 1 字节或少 1 字节都会导致 400 或超时)
multipart/form-data 请求体要手写 boundary 和换行符
WinInet 不解析表单结构,HttpSendRequest 只负责发原始字节。你得自己生成符合 RFC 7578 的 multipart body,关键点在于:换行必须是 \r\n(不是 \n),boundary 字符串需全局唯一,且开头必须带 --,结尾边界后还要额外加 --\r\n。
示例片段(C++ 字符串拼接):
立即学习“C++免费学习笔记(深入)”;
std::string boundary = "----WebKitFormBoundary" + generate_random_string(16); std::string body; body += "--" + boundary + "\r\n"; body += "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n"; body += "Content-Type: text/plain\r\n\r\n"; body += file_content; // 原始二进制数据,不转义 body += "\r\n--" + boundary + "--\r\n";
注意:Content-Type 后必须有两个 \r\n(空行),结尾边界后也必须有 \r\n;Windows 上用 std::ofstream 以 std::ios::binary 模式写文件时,别让 stream 自动转换 \n → \r\n,否则会重复换行。
- boundary 里避免出现
--或\r\n--,否则服务器解析错乱 - 文件名含中文?确保用 UTF-8 编码,并在
Content-Disposition中加filename*=UTF-8''...(但老服务器可能不支持,稳妥起见用 ASCII 文件名) - 二进制文件内容不能经过
std::string隐式截断(含\0),要用std::vector<char>或std::string_view管理原始字节
HttpSendRequest 失败返回 ERROR_INTERNET_INVALID_OPTION 是因为 header 格式不对
这个错误常被误认为网络问题,实际几乎全是请求头格式违规:lpszHeaders 参数传入的 header 字符串必须是 CRLF 分隔的纯文本,每行末尾必须是 \r\n,最后一行后**不能**有额外 \r\n,且 header 和 body 之间必须有一个空行(即两个连续 \r\n)。
典型错误写法:
// ❌ 错误:header 结尾多了一个 \r\n,或用了 \n "Content-Type: multipart/form-data; boundary=xxx\r\n\r\n" // ✅ 正确:header 块结束于 \r\n,空行是第二个 \r\n,之后紧跟 body "Content-Type: multipart/form-data; boundary=xxx\r\n"
调试技巧:把 header + body 全部写入本地文件,用记事本(非 Notepad++)打开确认换行符是否为 CRLF;或用 Wireshark 抓包比对标准浏览器请求。
- 不要在 header 中手动写
Content-Length——除非你确定 body 长度精确,否则让 WinInet 自动计算更安全(此时传 NULL 给lpszHeaders的 length 字段) - 若需设置 cookie 或认证,用
HttpAddRequestHeaders单独调用,别硬塞进lpszHeaders - HTTPS 站点记得在
InternetConnect时用INTERNET_DEFAULT_HTTPS_PORT和SECURITY_FLAG_IGNORE_UNKNOWN_CA(仅测试)
大文件上传要设超时并检查 InternetSetOption
默认 WinInet 连接超时仅 30 秒,上传百兆文件必然失败。必须在 HINTERNET hConnect 或 HINTERNET hRequest 句柄上分别设置连接、发送、接收超时:
DWORD timeout = 600000; // 10 分钟 InternetSetOption(hRequest, INTERNET_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout)); InternetSetOption(hRequest, INTERNET_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout));
另外,WinInet 默认禁用 Keep-Alive,每次上传都重建 TCP 连接,小文件不明显,大文件下握手开销显著。可手动开启:
std::string keepAliveHeader = "Connection: keep-alive\r\n"; HttpAddRequestHeaders(hRequest, keepAliveHeader.c_str(), ~0U, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
- 超时值单位是毫秒,设成 0 表示无限等待(不推荐)
-
INTERNET_OPTION_CONNECT_TIMEOUT对上传影响小,重点调SEND和RECEIVE - Keep-Alive 开启后,服务器响应头也必须含
Connection: keep-alive,否则 WinInet 下次请求仍会重连
边界情况容易被忽略:上传中途网络断开,HttpSendRequest 返回 FALSE,但 GetLastError() 可能是 ERROR_INTERNET_CONNECTION_ABORTED 而非 ERROR_INTERNET_TIMEOUT——得根据具体错误码决定重试还是报错。


















