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

使用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,求排查问题。


问题排查与修正

  1. 表单结构错误
    当前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\n
    
  2. Content-Length计算错误
    原代码计算长度时将文件大小与错误的formDataBody长度相加,实际应计算:头部长度 + 文件大小 + 结束边界长度。

  3. 宽字符串编码问题
    使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 18:34:52