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