使用WinHTTP的C++向Flask发送multipart/form-data失败排查
解决WinHTTP发送multipart/form-data时Flask无法识别request.files的问题
问题现象
用C++ WinHTTP向Flask服务器发送文件上传请求(multipart/form-data格式):
- Flask端通过
request.files获取文件时返回ImmutableMultiDict([]),完全无法识别文件字段 - 但通过
request.get_data()或request.data能拿到完整的请求原始数据 - 期望
request.files能正确解析出文件,输出类似:ImmutableMultiDict([('file', <FileStorage: 'img.png' ('image/png')>)])
错误原因
- 编码不匹配:HTTP协议要求multipart/form-data的边界、头部必须使用单字节ASCII编码,但原代码用
std::wstring构造请求头部和表单数据,发送时按UTF-16宽字节传输,导致Flask无法正确解析边界分隔符。 - 文件路径错误:原代码中
filePath = L"C:img.png"缺少路径分隔符,应为L"C:\\img.png",会导致文件名解析异常。 - Content-Length计算与实际发送字节数不匹配:宽字符串的字节数计算逻辑正确,但因编码问题实际发送的内容和声明的长度不符,导致Flask解析中断。
修复方案
1. 统一使用单字节字符串处理HTTP请求内容
将所有HTTP头部、表单边界、表单描述部分从std::wstring转换为std::string,确保以ASCII编码传输。
2. 修正文件路径格式
补全文件路径的分隔符,避免文件名错误。
3. 确保multipart格式符合规范
严格遵循RFC 7578规范:每个表单部分以--boundary开头,后跟头部字段,空行分隔头部和文件内容,最后以--boundary--结束。
修正后的完整C++代码
#include <windows.h> #include <winhttp.h> #include <iostream> #include <vector> #include <string> #include <sstream> #pragma comment(lib, "winhttp.lib") // 读取文件内容到vector<char> std::vector<char> ReadFileContent(const std::wstring& filePath) { std::vector<char> content; HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { return content; } DWORD fileSize = GetFileSize(hFile, NULL); content.resize(fileSize); DWORD bytesRead = 0; ReadFile(hFile, content.data(), fileSize, &bytesRead, NULL); CloseHandle(hFile); return content; } // 宽字符串转多字节字符串 std::string WideToMultiByte(const std::wstring& wstr) { int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL); std::string str(bufferSize, 0); WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, str.data(), bufferSize, NULL, NULL); // 去掉末尾的'\0' str.pop_back(); return str; } int main() { std::wstring filePath = L"C:\\img.png"; std::vector<char> fileContent = ReadFileContent(filePath); if (fileContent.empty()) { std::wcerr << L"Failed to read file!" << std::endl; return 1; } HINTERNET hSession = WinHttpOpen(L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { std::wcerr << L"WinHttpOpen failed!" << std::endl; return 1; } HINTERNET hConnect = WinHttpConnect(hSession, L"127.0.0.1", INTERNET_DEFAULT_HTTP_PORT, 0); if (!hConnect) { std::wcerr << L"WinHttpConnect failed!" << std::endl; WinHttpCloseHandle(hSession); return 1; } // 生成单字节的boundary std::string boundary = "--------------------------" + std::to_string(GetTickCount64()); // 构造HTTP头部 std::string headers = "Content-Type: multipart/form-data; boundary=" + boundary; // 构造表单头部:提取文件名,替换宽路径为文件名 size_t lastSlash = filePath.find_last_of(L"\\/"); std::wstring fileName = (lastSlash != std::wstring::npos) ? filePath.substr(lastSlash + 1) : filePath; std::string formData = "--" + boundary + "\r\n" "Content-Disposition: form-data; name=\"file\"; filename=\"" + WideToMultiByte(fileName) + "\"\r\n" "Content-Type: image/png\r\n\r\n"; std::string finalBoundary = "\r\n--" + boundary + "--\r\n"; HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/upload", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); if (!hRequest) { std::wcerr << L"WinHttpOpenRequest failed!" << std::endl; WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } DWORD dwOption = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; WinHttpSetOption(hRequest, WINHTTP_OPTION_REDIRECT_POLICY, &dwOption, sizeof(dwOption)); // 计算总内容长度:表单头部字节数 + 文件内容字节数 + 结束边界字节数 DWORD totalLength = formData.size() + fileContent.size() + finalBoundary.size(); BOOL bResults = WinHttpSendRequest(hRequest, headers.c_str(), headers.size(), 0, 0, totalLength, 0); if (!bResults) { std::wcerr << L"WinHttpSendRequest failed: " << GetLastError() << std::endl; WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } // 发送表单头部 bResults = WinHttpWriteData(hRequest, formData.data(), formData.size(), NULL); if (!bResults) { std::wcerr << L"WinHttpWriteData (formData) failed: " << GetLastError() << std::endl; WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } // 发送文件内容 bResults = WinHttpWriteData(hRequest, fileContent.data(), fileContent.size(), NULL); if (!bResults) { std::wcerr << L"WinHttpWriteData (file) failed: " << GetLastError() << std::endl; WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } // 发送结束边界 bResults = WinHttpWriteData(hRequest, finalBoundary.data(), finalBoundary.size(), NULL); if (!bResults) { std::wcerr << L"WinHttpWriteData (finalBoundary) failed: " << GetLastError() << std::endl; WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } bResults = WinHttpReceiveResponse(hRequest, NULL); if (!bResults) { std::wcerr << L"WinHttpReceiveResponse failed: " << GetLastError() << std::endl; WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } // 读取响应(可选) char buffer[1024]; DWORD bytesRead = 0; while (WinHttpReadData(hRequest, buffer, sizeof(buffer)-1, &bytesRead) && bytesRead > 0) { buffer[bytesRead] = '\0'; std::cout << "Response: " << buffer << std::endl; } // 清理资源 WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 0; }
Flask测试代码
from flask import Flask, request app = Flask(__name__) @app.route('/upload', methods=['POST']) def upload(): print("request.files:", request.files) print("request.content_type:", request.content_type) return "Upload Success" if __name__ == '__main__': app.run(host='127.0.0.1', port=80, debug=True)
验证结果
运行修正后的C++代码,Flask终端会输出:
request.files: ImmutableMultiDict([('file', <FileStorage: 'img.png' ('image/png')>)]) request.content_type: multipart/form-data; boundary=--------------------------123456789012345678901234
内容的提问来源于stack exchange,提问作者이진영
相关产品推荐
相关产品推荐

