c++spring-bootwinapiwinhttp

Post a multipart/form-data HTTP request with WinHTTP


I have been trying for the past few days to send an HTTP POST request to my SpringBoot application with the Win32 API, but I'm always receiving the same error. The request is a multipart consisting of a binary file and a JSON. Sending the request via Postman works with no problems, and I'm able to receive the file and the JSON correctly.

I have looked at almost every post and question regarding how to construct an HTTP request with WinHTTP, and it seems they all do exactly what I did, but for some reason I'm getting a weirdly constructed request, as seen in the WireShark image below.

It seems as if the request is not recognized as several parts, but rather as one chunk of data.

Looking in WireShark at the correct request which was sent with postman, shows that the request consists of all parts, just as it supposed to.

Here is my code:

LPCWSTR additionalHeaders = L"Accept: application/json\r\nContent-Type: multipart/form-data; boundary=----------------------------346435246262465368257857\r\n";

if (data->type == RequestType::MULTIPART) {
    size_t filesize = 0;
    char* fileData = fileToString("img.png", "rb", &filesize);

    WinHttpAddRequestHeaders(hRequest, additionalHeaders, -1L, WINHTTP_ADDREQ_FLAG_ADD);
      
    char postData1[] = 
        "----------------------------346435246262465368257857\r\n"
        "Content-Disposition: form-data; name=\"file\"; filename=\"img.png\"\r\n"
        "Content-Type: image/png\r\n\r\n";  
    char postData2[] = 
        "\r\n----------------------------346435246262465368257857\r\n"
        "Content-Disposition: form-data; name=\"newData\"\r\n"
        "Content-Type: application/json\r\n\r\n"
        "{\"dataType\":\"DEVICE_SETTINGS\"}"        
        "\r\n----------------------------346435246262465368257857--\r\n";

     
    if (hRequest)
        bResults = WinHttpSendRequest(hRequest,
            WINHTTP_NO_ADDITIONAL_HEADERS,
            0, WINHTTP_NO_REQUEST_DATA, 0,
            lstrlenA(postData1) + lstrlenA(postData2) + filesize, NULL);

    DWORD dwBytesWritten = 0; 
    if (bResults)
        bResults = WinHttpWriteData(hRequest, postData1, lstrlenA(postData1), &dwBytesWritten);
    if (bResults) 
        bResults = WinHttpWriteData(hRequest,(LPCVOID) fileData, filesize, &dwBytesWritten);
    if (bResults)
        bResults = WinHttpWriteData(hRequest, postData2, lstrlenA(postData2), &dwBytesWritten);
}

errorMessageID = ::GetLastError();

// End the request.
if (bResults)
    bResults = WinHttpReceiveResponse(hRequest, NULL);

Valid request sent with Postman

Invalid request sent with WinHTTP

Postman configuration 1

Postman configuration 2

SpringBoot controller

Here is the error I receive in my SpringBoot app log:

2022-03-04 14:48:34.520 WARN 25412 --- [nio-8010-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present]

I would really appreciate any help here solving this mystery!


Solution

  • The MIME boundaries in your postdata1 and postdata2 strings are incomplete, which is why WireShark and the SpringBoot app are not parsing your data correctly.

    Every MIME boundary in the body data must start with a leading --, followed by the value you specified in the Content-Type's boundary attribute, followed by a trailing -- in the final termination boundary.

    Let's look at an simpler example that doesn't use any - in the boundary value at all, this should make it clearer to you:

    POST /resource HTTP/1.1
    Host: ...
    Content-Type: multipart/form-data; boundary=myboundary\r\n";
    
    --myboundary
    Content-Disposition: form-data; name="file"; filename="img.png"
    Content-Type: image/png
    
    <file data>
    --myboundary
    Content-Disposition: form-data; name="newData"
    Content-Type: application/json
    
    <json data>
    --myboundary--
    

    As you can see above, and in Postman's data, the leading -- is present on each boundary, but is missing in your data:

    Hence, the leading -- is missing from each boundary in your data.

    Simply remove 2 -s from the value in your Content-Type's boundary attribute, and then you should be fine.