使用WinHttp&C++发送PNG至服务器失败,请求排查问题
问题:WinHttp上传PNG文件时Spring后端提示缺失'attachment'请求部件
请求服务器成功,但返回错误:
{"code":"9999","message":"org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'attachment' is not present","cause":".","correlationID":"***-***-43f4-82b2-***","apiURL":"POST : /file/4856.png","origin":"PartMfgServer","timestamp":1704814609769}
后端Spring接口代码:
@PostMapping(path = "/{fileName}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public String uploadFileFromServer(@RequestHeader("File-Type") String contentType, @PathVariable("fileName") String fileName, @RequestParam(value = "fileRole", required = false) FILE_ROLE fileRole, @RequestParam("attachment") MultipartFile fileToUpload) {.......}
我的C++上传代码:
int main() { LPCWSTR server = L"domain.com"; LPCWSTR path = L"api/v2307/file/4856.png"; HINTERNET hSession = WinHttpOpen(L"ImageUploader/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { return -1; } HINTERNET hConnect = WinHttpConnect(hSession, server, INTERNET_DEFAULT_HTTPS_PORT, 0); if (!hConnect) { std::cerr << "WinHttpConnect failed!" << std::endl; WinHttpCloseHandle(hSession); return -1; } HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", path, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); if (!hRequest) { // Handle connection error WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return -1; } // Open image file for reading HANDLE hFile = CreateFile(L"C:\\Users\\m5zmfk\\AppData\\Local\\Temp\\nxOpen\\partfiles\\4856\\4856.png", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { // Handle file open error WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return -1; } DWORD fileSize = GetFileSize(hFile, NULL); std::wstring boundary = L"---------------------------1234567890"; // std::wstring endBoundary = L"\r\n--" + boundary + L"--\r\n"; std::wstring formDataBody = L"--" + boundary + L"\r\n" L"Content-Disposition: form-data; name=\"attachment\"; filename=\"4856.png\"\r\n" L"\r\n--" + boundary + L"--\r\n"; std::wstring headers = L"authorization: ****\r\n" L"Content-Type: multipart/form-data; boundary=" + boundary + L"\r\n" L"File-Type: image/png\r\n" L"Content-Length: " + std::to_wstring(fileSize + formDataBody.length()) +L"\r\n"; bool success = WinHttpAddRequestHeaders(hRequest, headers.c_str(), headers.length(), WINHTTP_ADDREQ_FLAG_ADD); if (!success) { std::cout << "Error adding headers" << std::endl; printLastError(GetLastError()); } DWORD timeout = 60000 * 2; // Set timeout to 60 seconds (adjust as needed) WinHttpSetOption(hRequest, WINHTTP_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout)); WinHttpSetOption(hRequest, WINHTTP_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout)); if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { // Handle request send error printLastError(GetLastError()); CloseHandle(hFile); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return -1; } DWORD bytesWritten; // Build the form-data request body success = WinHttpWriteData(hRequest, formDataBody.c_str(), formDataBody.length(), &bytesWritten); if (!success) { printLastError(GetLastError()); CloseHandle(hFile); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return -1; } // Write the file content BYTE buffer[1024]; DWORD bytesRead; while (ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) { success = WinHttpWriteData(hRequest, buffer, bytesRead, &bytesWritten); if (!success) { printLastError(GetLastError()); CloseHandle(hFile); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return -1; } } if (!WinHttpReceiveResponse(hRequest, NULL)) { // Handle response error printLastError(GetLastError()); CloseHandle(hFile); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return -1; } // Read and print the response DWORD resRead; BYTE resBuffer[1024]; while (WinHttpReadData(hRequest, resBuffer, sizeof(resBuffer), &resRead) && resRead > 0) { // Process the received data (you can print it or save it to a file, etc.) std::cout.write(reinterpret_cast<const char*>(resBuffer), resRead); } // Close the file and WinHTTP handles CloseHandle(hFile); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 0; }
此前尝试将formDataBody通过WinHttpAddRequestHeaders作为请求头添加时,得到错误码87,求排查问题。
问题排查与修正
表单结构错误
当前formDataBody在文件头部后直接添加了结束边界,导致文件内容被放在结束边界之后,服务器无法识别attachment部件的有效内容。正确的multipart结构应为:--boundary\r\n Content-Disposition: form-data; name="attachment"; filename="4856.png"\r\n Content-Type: image/png\r\n \r\n [文件二进制内容] \r\n--boundary--\r\nContent-Length计算错误
原代码计算长度时将文件大小与错误的formDataBody长度相加,实际应计算:头部长度 + 文件大小 + 结束边界长度。宽字符串编码问题
使用std::wstring构建表单头部会导致WinHttpWriteData发送UTF-16编码字节,而multipart/form-data要求头部为ASCII编码,需改用std::string。
修正后的关键代码片段
// 改用std::string避免编码问题 std::string boundary = "---------------------------1234567890"; std::string partHeader = "--" + boundary + "\r\n" "Content-Disposition: form-data; name=\"attachment\"; filename=\"4856.png\"\r\n" "Content-Type: image/png\r\n" "\r\n"; std::string endBoundary = "\r\n--" + boundary + "--\r\n"; // 计算正确的Content-Length DWORD totalLength = partHeader.size() + fileSize + endBoundary.size(); // 构建请求头(注意转换为宽字符串) std::wstring headers = L"authorization: ****\r\n" L"Content-Type: multipart/form-data; boundary=" + std::wstring(boundary.begin(), boundary.end()) + L"\r\n" L"File-Type: image/png\r\n" L"Content-Length: " + std::to_wstring(totalLength) + L"\r\n"; // 发送请求后,先写partHeader success = WinHttpWriteData(hRequest, partHeader.c_str(), partHeader.size(), &bytesWritten); if (!success) { /* 错误处理 */ } // 写文件内容(原代码不变) BYTE buffer[1024]; DWORD bytesRead; while (ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) { success = WinHttpWriteData(hRequest, buffer, bytesRead, &bytesWritten); if (!success) { /* 错误处理 */ } } // 最后写结束边界 success = WinHttpWriteData(hRequest, endBoundary.c_str(), endBoundary.size(), &bytesWritten); if (!success) { /* 错误处理 */ }
额外说明
错误码87是ERROR_INVALID_PARAMETER,将formDataBody放到请求头里本身就是错误的,请求头不能包含请求体内容,所以必然会参数无效。
内容的提问来源于stack exchange,提问作者suresh kumar
相关产品推荐
相关产品推荐

