Sending a HTTP POST with a file >2GB from VBA - vba

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

Related

Unable to upload a text file using vba [duplicate]

This question already has an answer here:
Convert CURL command line to VBA
(1 answer)
Closed 2 years ago.
I'm trying to upload a tiny text file in a website using vba. When I run the script I encounter this error {"success":false,"error":400,"message":"Trouble uploading file"}. I mimicked the same approach using vba that I did and found success using python. I got rid of the headers altogether in python so I suppose multipart headers is not that important to upload the file successfully.
Using vba (I got the above error):
Sub UploadFile()
Dim Http As New XMLHTTP60, sPostData$
Dim nFile&, baBuffer() As Byte
nFile = FreeFile
Open "C:\Users\WCS\Desktop\some_text.txt" For Binary Access Read As nFile
If LOF(nFile) > 0 Then
ReDim baBuffer(0 To LOF(nFile) - 1) As Byte
Get nFile, , baBuffer
sPostData = StrConv(baBuffer, vbUnicode)
End If
Close nFile
' MsgBox sPostData 'to examine if it is able to print the text
With Http
.Open "POST", "https://file.io/"
.setRequestHeader "x-requested-with", "XMLHttpRequest"
.send ("file=" & sPostData)
Debug.Print .responseText
End With
End Sub
Using vba (another way but got the same error):
Sub UploadFile()
Dim Http As New XMLHTTP60, sPostData$
With CreateObject("ADODB.Stream")
.Charset = "utf-8"
.Open
.LoadFromFile ("C:\Users\WCS\Desktop\some_text.txt")
sPostData = .ReadText()
End With
With Http
.Open "POST", "https://file.io/"
.setRequestHeader "x-requested-with", "XMLHttpRequest"
.send ("file=" & sPostData)
Debug.Print .responseText
End With
End Sub
Using python (I got success):
import requests
url = 'https://file.io/'
files = {
'file': open('some_text.txt','rb')
}
def upload_file(link):
res = requests.post(link,files=files)
print(res.content)
if __name__ == '__main__':
upload_file(url)
Btw, this is what the text file contains hi there!!!
If the file was textfile, you can store the contents in a variable and in this case to send the contents as text
Sub UploadFile()
Dim http As New XMLHTTP60, sPostData$
With CreateObject("ADODB.Stream")
.Charset = "UTF-8"
.Open
.LoadFromFile (ThisWorkbook.Path & "\Sample.txt")
sPostData = .ReadText()
End With
With http
.Open "POST", "https://file.io"
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.send ("text=" & sPostData)
Debug.Print .responseText
End With
End Sub
The code depends on the code derived from this LINK

Sending Photo to Telegram (API / Bot)

I send messages form Excel to telegram. It works nice.
But how can I send a photo? I don't understand it (https://core.telegram.org/bots/api#sendphoto)
Thanks for help!
My send Message:
Dim objRequest As Object
Dim strChatId As String
Dim strMessage As String
Dim strPostData As String
Dim strResponse As String
strChatId = Worksheets("Einstellungen").Cells(3, "AB")
strMessage = Report
APIcode = Worksheets("Einstellungen").Cells(2, "AB")
strPostData = "chat_id=" & strChatId & "&text=" & strMessage
Set objRequest = CreateObject("MSXML2.XMLHTTP")
With objRequest
.Open "POST", "https://api.telegram.org/" & APIcode & "/sendMessage?", False
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.send (strPostData)
GetSessionId = .responseText
End With
If your code is working as-is for plain text messages then you should only need to make a couple changes to it.
You're probably currently using the API's sendMessage method, which takes the chat_id and text parameters.
You want to use the sendPhoto method, which tales the chat_id and photo parameters (but no text parameter).
So this is a bit of a shot in the dark since I've never used or heard of Telegram and I don't have a key, so I can't test it, but theoretically, you could send a photo from a URL like this:
Sub telegram_SendPhoto()
Const photoURL = "https://i.imgur.com/0eH6d1v.gif" 'URL of photo
Dim objRequest As Object, strChatId As String, APIcode As String
Dim strPostData As String, strResponse As String
strChatId = Worksheets("Einstellungen").Cells(3, "AB")
APIcode = Worksheets("Einstellungen").Cells(2, "AB")
strPostData = "chat_id=" & strChatId & "&photo=" & photoURL
Set objRequest = CreateObject("MSXML2.XMLHTTP")
With objRequest
.Open "POST", "https://api.telegram.org/" & APIcode & "/sendPhoto?", False
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.send (strPostData)
strResponse = .responseText
End With
MsgBox strResponse
End Sub
Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet (above), or upload a new photo using multipart/form-data. More info on Sending Files ยป

How to Pass byte[] from Excel vba to web service

i want to pass byte[] from excel vba to web service.
Below is the code which will convert file to byte[].
Dim bytFile() As Byte
bytFile = GetFileBytes("C:\test.doc")
below code is used to call webservice. bytFile is my parameter
Dim XMLHttp As Object: Set XMLHttp = CreateObject("Microsoft.XMLHTTP")
XMLHttp.Open "POST", Service + "/PassExcelData", False
XMLHttp.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
XMLHttp.send "filebyte=" & bytFile
But i am not able to pass it to webservice. i am getting error at last line. So i just want to is it possible? If not, which way i can achieve this?
I have also tried below code
Function FileToStr(ByVal strFile As String) As String
Dim hFile As Long
hFile = FreeFile
Open strFile For Input As #hFile
FileToStr = Input$(LOF(hFile), hFile)
Close #hFile
End Function
and called it as:
XMLHttp.send "filebyte=" & FileToStr(file path).
but it returned the below error:
You can't append a byte array with & operator. get the text of the file using
use this function
Function FileToStr(ByVal strFile As String) As String
Dim hFile As Long
hFile = FreeFile
Open strFile For Input As #hFile
FileToStr = Input$(LOF(hFile), hFile)
Close #hFile
End Function
and then do
XMLHttp.send "filebyte=" & FileToStr("Path to your file")
You can comment out the GetFileBytes line

Attachments using REST WebService and VB.NET

I am currently developing an application using VB.NET in which I am using the REST WebServices. I have been able to do the basics with REST, however, I have not been able to add an attachment (more specifically upload a file, using REST which gets attached). I have done extensive research online, but so far I have not been able to find any working examples in VB.NET. To actually upload the data I use System.Net.WebClient. The following VB.NET code does the important work:
Dim Client As New System.Net.WebClient
Dim postBytes As Byte() = System.Text.Encoding.ASCII.GetBytes(postString)
Client.UploadData(URL, "POST", postBytes)
A simplified version of my URL is as follows:
"../REST/1.0/ticket/" + ticketNumber + "/comment?user=" + userName + "&pass=" + password
Finally, an example of the content that I post is:
postString = "content=Text: RT Test" + vbLf + "Action: Comment" + vbLf + "Attachment: examplefile.jpg" + vbLf + "attachment_1="
As you can see, the postString is converted to bytes and then uploaded to the server. However, I do not know where or how I should be posting the raw attachment itself. The documentation for the service we are specifically using states to use a variable "attachment_1," which I added to the postString variable, but I am not sure what the next step should be. Should the file be converted into bytes and appended to the postBytes variable? I attempted something like this but I received an error saying that no attachment was found for examplefile.jpg.
Thanks for your help!
We could not use Client.UploadData(...) and had to convert the entire post to bytes, starting with the POST fields before the attachment, then the attachment itself, and finally the remainder of the POST fields.
Public Sub AddAttachmentToRT(ByVal url As String, ByVal fileName As String, ByVal filePath As String)
Dim dataBoundary As String = "--xYzZY"
Dim request As HttpWebRequest
Dim fileType As String = "image/jpeg" 'Will want to extract this to make it more generic from the uploaded file.
'Create a POST web request to the REST interface using the passed URL
request = CType(WebRequest.Create(url), HttpWebRequest)
request.ContentType = "multipart/form-data; boundary=xYzZY"
request.Method = "POST"
request.KeepAlive = True
'Write the request to the requestStream
Using requestStream As IO.Stream = request.GetRequestStream()
'Create a variable "attachment_1" in the POST, specify the file name and file type
Dim preAttachment As String = dataBoundary + vbCrLf _
+ "Content-Disposition: form-data; name=""attachment_1""; filename=""" + fileName + """" + vbCrLf _
+ "Content-Type: " + fileType + vbCrLf _
+ vbCrLf
'Convert this preAttachment string to bytes
Dim preAttachmentBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(preAttachment)
'Write this preAttachment string to the stream
requestStream.Write(preAttachmentBytes, 0, preAttachmentBytes.Length)
'Write the file as bytes to the stream by passing its exact location
Using fileStream As New IO.FileStream(Server.MapPath(filePath + fileName), IO.FileMode.Open, IO.FileAccess.Read)
Dim buffer(4096) As Byte
Dim bytesRead As Int32 = fileStream.Read(buffer, 0, buffer.Length)
Do While (bytesRead > 0)
requestStream.Write(buffer, 0, bytesRead)
bytesRead = fileStream.Read(buffer, 0, buffer.Length)
Loop
End Using
'Create a variable named content in the POST, specify the attachment name and comment text
Dim postAttachment As String = vbCrLf _
+ dataBoundary + vbCrLf _
+ "Content-Disposition: form-data; name=""content""" + vbCrLf _
+ vbCrLf _
+ "Action: comment" + vbLf _
+ "Attachment: " + fileName + vbCrLf _
+ "Text: Some description" + vbCrLf _
+ vbCrLf _
+ "--xYzZY--"
'Convert postAttachment string to bytes
Dim postAttachmentBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(postAttachment)
'Write the postAttachment string to the stream
requestStream.Write(postAttachmentBytes, 0, postAttachmentBytes.Length)
End Using
Dim response As Net.WebResponse = Nothing
'Get the response from our REST request to RT
'Required to capture response, without this Try-Catch attaching will fail
Try
response = request.GetResponse()
Using responseStream As IO.Stream = response.GetResponseStream()
Using responseReader As New IO.StreamReader(responseStream)
Dim responseText = responseReader.ReadToEnd()
End Using
End Using
Catch exception As Net.WebException
response = exception.Response
If (response IsNot Nothing) Then
Using reader As New IO.StreamReader(response.GetResponseStream())
Dim responseText = reader.ReadToEnd()
End Using
response.Close()
End If
Finally
request = Nothing
End Try
End Sub

tcpclient vs httpwebrequest

I used a tcpclient to make a connection to a streaming API and for some reason it doesn't work with a 301 error, (something wrong with my credentials). However when I use a httpwebrequest to the same API and use the same credentials and that works. I am trying to figure out what I am doing wrong:
TCPclient connection:
Try
Dim bufferread(defaultSize) As Byte
url = "xxxxxxxxx.com"
Dim tclient As TcpClient = New TcpClient(url, "80")
' use a network stream to download the tcpClient stream
nstream = tclient.GetStream()
' check if we can write to the stream to add the relevant headers and credentials
If nstream.CanWrite Then
Dim headers As String
headers = "GET " & addedUrl & " HTTP/1.0" & Chr(13) & "" & Chr(10)
headers &= "Authorization: Basic " & userNamePassword & Chr(13) & "" & Chr(10)
headers &= Chr(13) & "" & Chr(10)
Dim sendBytes As [Byte]() = Encoding.UTF8.GetBytes(headers)
nstream.Write(sendBytes, 0, sendBytes.Length)
If nstream.CanRead Then
Dim timestamp As DateTime = DateTime.Now
Dim data As String
numbytesRead = 0
' start reading from the stream
Do....
httpwebrequest:
While Not responseData = Nothing
Try
' setup the webrequest and headers to send
url = "https://xxxxxxxxxxxx.com" & addedUrl
If Not parsingTools.refreshDate = Nothing Then
url = parsingTools.refreshDate
End If
Dim poststring As String = ""
webrequest = TryCast(System.Net.WebRequest.Create(url), HttpWebRequest)
webrequest.Method = "GET"
webrequest.UserAgent = "xxxxxxxxxx"
webrequest.Referer = "xxxxxxxxxxxxx"
webrequest.Timeout = 20000
webrequest.KeepAlive = True
webrequest.Credentials = New System.Net.NetworkCredential ("xxxxxxxxxxxxx", "yyyyyyyyyyyyyy")
'get the responsestream
responseStream = webrequest.GetResponse().GetResponseStream()
'check if stream is readable
If responseStream.CanRead Then
HTTP 301 is not an error, it's a redirect. HttpWebRequest can handle redirects transparently but if you are doing all the HTTP implementation yourself with TcpClient then you need to parse and follow the redirect manually.