0

I try to upload a file (POST request) to a web service through their API in VBA. I get an "out of memory error" if the file is too large.

Unfortunately the web service does not support chunked transfer (which seems to be deprecated since http/2 anyway). Is there a way to "stream" the file without loading it into memory at once before sending?

I would like to avoid calling curl.exe, because it's cleaner.

    Dim url As String
    Dim dataStream As Object
    
    Const adTypeBinary = 1
    url = "https://api-endpoint.domain.com"
    
    Dim oWinHttpReq As Object
    Set oWinHttpReq = CreateObject("WinHttp.WinHttpRequest.5.1")

    With oWinHttpReq
        .Open "POST", url, False
        .setRequestHeader "Content-Type", "application/octet-stream"
        .setRequestHeader "Authorization", "Bearer " & AUTH_TOKEN
    
        Set dataStream = CreateObject("ADODB.Stream")
        
        dataStream.Type = adTypeBinary
        dataStream.Open
        dataStream.LoadFromFile filePath
        
        .send dataStream.Read
    End With

I tried Win32 file API as well but cannot transfer the output to the HTTP post.

/edit: I think I came closer. The solution seems to be chunked transfer - by setting the Header "Transfer-Encoding" to "chunked". Looks like you have to handle the chunk structure yourself because WinHTTP 5.1 does only support chunked download, not chunked upload. How do I use the winhttp api with "transfer-encoding: chunked"

But when I set the "Transfer-Encoding" Header, the .send method does not seem to be present anymore

/edit: I developed this function with the help of ChatGPT, but the HttpSendRequest fails...


Public Sub UploadFileChunkedLarge(filePath As String, url As String)
    Dim CHUNK_SIZE As Long
    CHUNK_SIZE = CLng(1024) * CLng(1024)   ' 1 MB
    
    Dim hSession As Long
    Dim hRequest As Long
    Dim hConnection As Long
    Dim lngRetVal As Long
    Dim strBoundary As String
    Dim strPost As String
    Dim strHeader As String
    Dim varData() As Byte
    Dim lngIndex As Long
    Dim lngSize As Long
    Dim lngBytesRead As Long
    
    Dim result As Boolean

    ' Set the boundary for the POST data
    strBoundary = "---------------------------7d93b2a700d04"

    ' Open the file for binary access
    Open filePath For Binary Access Read As #1

    ' Get the file size
    lngSize = LOF(1)

    ' Create the session
    hSession = InternetOpen("Upload", INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)

    ' Create the request
    hConnection = InternetConnect(hSession, url, INTERNET_DEFAULT_HTTP_PORT, vbNullString, vbNullString, INTERNET_SERVICE_HTTP, 0, 0)
   ' HttpOpenRequest hRequest, "POST", "", "HTTP/1.1", "", "", INTERNET_FLAG_NO_CACHE_WRITE Or INTERNET_FLAG_NO_AUTH, 0
    hRequest = HttpOpenRequest(hConnection, "POST", "", "HTTP/1.1", "", "", INTERNET_FLAG_NO_CACHE_WRITE, 0)

    ' Add the headers
    strHeader = "Content-Type: multipart/form-data; boundary=" & strBoundary & vbCrLf
    strHeader = strHeader & "Authorization: Bearer " & KDRIVE_TOKEN & vbCrLf
    strHeader = strHeader & "Content-Length: " & lngSize & vbCrLf & vbCrLf
    result = HttpAddRequestHeaders(hRequest, strHeader, Len(strHeader), HTTP_ADDREQ_FLAG_ADD)
    Debug.Print WININET_GetLastError
    ' Send the request
    result = HttpSendRequest(hRequest, vbNullString, 0, vbNullString, 0)
    Debug.Print WININET_GetLastError

    ' Send the file data in chunks
    Do While Not EOF(1)
        ' Read the next chunk of data
        ReDim varData(CHUNK_SIZE)
        lngBytesRead = LOF(1) - Loc(1)
        If lngBytesRead > CHUNK_SIZE Then
            lngBytesRead = CHUNK_SIZE
        End If
        Get #1, , varData

        ' Send the chunk
        result = InternetWriteFile(hRequest, varData(0), lngBytesRead, lngIndex)
    Loop

    ' Close the file
    Close #1

    ' Close the request
    InternetCloseHandle hRequest

    ' Close the session
    InternetCloseHandle hSession
End Sub
  • Does this answer your question? [How to send files via HTTP\_POST with Excel using VBA?](https://stackoverflow.com/questions/10954293/how-to-send-files-via-http-post-with-excel-using-vba) – June7 Jan 13 '23 at 19:30
  • Thanks for the hint. Unfortunately not, because both examples try to load the whole file into memory, which fails. – Martin Schneider Jan 13 '23 at 22:56
  • One of the answers provided references ADODB.Stream. That sounded relevant. – June7 Jan 13 '23 at 23:00
  • Check [`HttpSendRequestEx`](https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-httpsendrequestexw) and [`InternetWriteFile`](https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetwritefile). Unfortunately it's a bit more difficult than the normal WinAPI way. Afaik stepwise writes are not supported through the COM interface. You might also want to check if your server has a way to handle chunking, servers that expect large files usually have a way to upload one chunk per request to allow retries and partial uploads – Erik A Jan 14 '23 at 08:46
  • I found out that the API is equipped with a different set of calls to handle chunked upload - the user does not have to handle the chunked transfer himself. – Martin Schneider Jan 20 '23 at 09:55

0 Answers0