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
Related
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
When i wrote a http get request in vba to get the session id , instead of getting the session id , i am getting an HTML code in the immediate window?
Why is this so?
So basically , when you open the link on browser it will first ask the user to enter his username and password , then it will show him the session id.
But when i select the link in vba code , it shows me the html code
Sub Button1_Click()
Dim objRequest As Object
Dim strUrl As String
Dim blnAsync As Boolean
Dim strResponse As String
Set objRequest = CreateObject("MSXML2.XMLHTTP")
strUrl = "url to be entered"
blnAsync = True
With objRequest
.Open "GET", strUrl, blnAsync
.SetRequestHeader "Content-Type", "application/json"
.Send
'spin wheels whilst waiting for response
While objRequest.readyState <> 4
DoEvents
Wend
strResponse = .ResponseText
End With
Debug.Print strResponse
End Sub
After some help from stackoverflow experts I have been able to successfully retrieve my response using SOAP. The below piece is how I received and stored the data. This of course is not all the code. I just included this to show how I later reference the xml.
With xmlhtp
webserviceSOAPActionNameSpace = "http://example.com/webservices/"
.Open "POST", sUrl, False
.setRequestHeader "POST", "https://onesite.example.com/webservices/stuff.asmx HTTP/1.1"
.setRequestHeader "Content-Type", "text/xml; charset=utf-8"
.setRequestHeader "Content-Length", 100
.setRequestHeader "SOAPAction", webserviceSOAPActionNameSpace & "RetrieveData"
.send sEnv
sResult = xmlhtp.statusText
responseText = xmlhtp.responseText
ActiveSheet.Cells(1, 1).Value = .responseText
End With
Debug.Print responseText
Now I am having trouble parsing that out. This seems like it should be pretty simple but I get an error indicating that the responseText I receive above is not "loading" to xmlDOC. The following is at the beginning of the sub:
Dim xmlhtp As New MSXML2.XMLHTTP
Dim xmlDoc As New DOMDocument
Dim XDoc As Object
After the With End (shown above) my code looks like this:
Set XDoc = CreateObject("MSXML2.DOMDocument")
XDoc.async = False: XDoc.validateOnParse = False
XDoc.Load (xmlhtp.responseText)
Set lists = XDoc.DocumentElement
Set getFirstChild = lists.FirstChild
Debug.Print getFirstChild.XML
Debug.Print getFirstChild.Text
On the line
Set getFirstChild = lists.FirstChild
I recieve the following error
Object variable or With block variable not set
When I look at the Local Variable window in VBA I can clearly see that nothing was assigned to xmlDoc. So I assume my problem is in XDoc.Load Line.
Any direction would be appreciated.
use XDoc.LoadXML (xmlhtp.responseText) instead of XDoc.Load (xmlhtp.responseText)
first time posting,
I'm trying to get the ID "dadosDoUsuario" from a website's page I have to be logged in. I got it working using "InternetExplorer.Application" object, but can't get the ID value when using "MSXML2.XMLHTTP" object. It seems it won't go past the login page, since I'm able to get other IDs from this page (example: "tituloPagina"). Could someone give a hint on how I get the data from the page after logged in? Thanks!
InternetExplorer.Application code (this one works):
Sub testIE()
Dim texto As String
Set ie = CreateObject("InternetExplorer.Application")
my_url = "https://www.nfp.fazenda.sp.gov.br/login.aspx"
With ie
.Visible = False
.Navigate my_url
Do Until Not ie.Busy And ie.readyState = 4
DoEvents
Loop
End With
ie.Document.getelementbyid("userName").Value = "MYUSERNAME"
ie.Document.getelementbyid("Password").Value = "MYPASSWORD"
ie.Document.getelementbyid("Login").Click
Do Until Not ie.Busy And ie.readyState = 4
DoEvents
Loop
ie.Document.getelementbyid("btnConsultarNFSemestre").Click
Do Until Not ie.Busy And ie.readyState = 4
DoEvents
Loop
texto = ie.Document.getelementbyid("dadosDoUsuario").innerText
MsgBox texto
ie.Quit
End Sub
MSXML2.XMLHTTP code (this one doesn't work):
Sub testXMLHTTP()
Dim xml As Object
Dim html As Object
Dim dados As Object
Dim text As Object
Set xml = CreateObject("MSXML2.XMLHTTP")
Set html = CreateObject("htmlFile")
With xml
.Open "POST", "https://www.nfp.fazenda.sp.gov.br/Login.aspx", False
.setRequestHeader "Content-Type", "text/xml"
.send "userName=MYUSERNAME&password=MYPASSWORD"
.Open "GET", "https://www.nfp.fazenda.sp.gov.br/Inicio.aspx", False
.setRequestHeader "Content-Type", "text/xml"
.send
End With
html.body.innerhtml = xml.responseText
Set objResult = html.GetElementById("dadosDoUsuario")
GetElementById = objResult.innertext
MsgBox GetElementById
End Sub
EDIT: I followed the steps suggested by #Florent B., and added a scripcontrol to get the encoded values for __VIEWSTATE, __VIEWSTATEGENERATOR and __EVENTVALIDATION. Got it working!
Sub testXMLHTTP()
Dim xml As Object
Dim html As HTMLDocument
Dim dados As Object
Dim text As Object
Dim html2 As HTMLDocument
Dim xml2 As Object
Set xml = CreateObject("Msxml2.ServerXMLHTTP.6.0")
Set html = CreateObject("htmlFile")
With xml
.Open "GET", "https://www.nfp.fazenda.sp.gov.br/Login.aspx", False
.send
End With
strCookie = xml.getResponseHeader("Set-Cookie")
html.body.innerhtml = xml.responseText
Set objvstate = html.GetElementById("__VIEWSTATE")
Set objvstategen = html.GetElementById("__VIEWSTATEGENERATOR")
Set objeventval = html.GetElementById("__EVENTVALIDATION")
vstate = objvstate.Value
vstategen = objvstategen.Value
eventval = objeventval.Value
'URL Encode ViewState
Dim ScriptEngine As ScriptControl
Set ScriptEngine = New ScriptControl
ScriptEngine.Language = "JScript"
ScriptEngine.AddCode "function encode(vstate) {return encodeURIComponent(vstate);}"
Dim encoded As String
encoded = ScriptEngine.Run("encode", vstate)
vstate = encoded
'URL Encode Event Validation
ScriptEngine.AddCode "function encode(eventval) {return encodeURIComponent(eventval);}"
encoded = ScriptEngine.Run("encode", eventval)
eventval = encoded
'URL Encode ViewState Generator
ScriptEngine.AddCode "function encode(vstategen) {return encodeURIComponent(vstategen);}"
encoded = ScriptEngine.Run("encode", vstategen)
vstategen = encoded
Postdata = "__EVENTTARGET=" & "&__EVENTARGUMENT=" & "&__VIEWSTATE=" & vstate & "&__VIEWSTATEGENERATOR=" & vstategen & "&__EVENTVALIDATION=" & eventval & "&ctl00$ddlTipoUsuario=#rdBtnNaoContribuinte" & "&ctl00$UserNameAcessivel=Digite+o+Usuário" & "&ctl00$PasswordAcessivel=x" & "&ctl00$ConteudoPagina$Login1$rblTipo=rdBtnNaoContribuinte" & "&ctl00$ConteudoPagina$Login1$UserName=MYUSERNAME" & "&ctl00$ConteudoPagina$Login1$Password=MYPASSWORD" & "&ctl00$ConteudoPagina$Login1$Login=Acessar" & "&ctl00$ConteudoPagina$Login1$txtCpfCnpj=Digite+o+Usuário"
Set xml2 = CreateObject("Msxml2.ServerXMLHTTP.6.0")
Set html2 = CreateObject("htmlFile")
With xml2
.Open "POST", "https://www.nfp.fazenda.sp.gov.br/Login.aspx", False
.setRequestHeader "Cookie", strCookie
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.setRequestHeader "Content-Lenght", Len(Postdata)
.send (Postdata)
End With
html2.body.innerhtml = xml2.responseText
Set objResult = html2.GetElementById("dadosDoUsuario")
GetElementById = objResult.innertext
MsgBox GetElementById
End Sub
It's possible but not that easy.
First you need to use CreateObject("Msxml2.ServerXMLHTTP.6.0") and not CreateObject("MSXML2.XMLHTTP").
Then follow these steps:
Open and send a GET to https://www.nfp.fazenda.sp.gov.br/login.aspx
Parse and store the cookie from the response header "Set-Cookie"
Parse and store the __VIEWSTATE, __VIEWSTATEGENERATOR, __EVENTVALIDATION from the HTML response
Build the data for the next query with the values parsed previously and with your user-name/password :
__EVENTTARGET:""
__EVENTARGUMENT:""
__VIEWSTATE:"..."
__VIEWSTATEGENERATOR:"..."
__EVENTVALIDATION:"..."
ctl00$ddlTipoUsuario:"#rdBtnNaoContribuinte"
ctl00$UserNameAcessivel:"Digite+o+Usuário"
ctl00$PasswordAcessivel:"x"
ctl00$ConteudoPagina$Login1$rblTipo:"rdBtnNaoContribuinte"
ctl00$ConteudoPagina$Login1$UserName:"..."
ctl00$ConteudoPagina$Login1$Password:"..."
ctl00$ConteudoPagina$Login1$Login:"Acessar"
ctl00$ConteudoPagina$Login1$txtCpfCnpj:"Digite+o+Usuário"
Open a POST to https://www.nfp.fazenda.sp.gov.br/login.aspx
Set the header "Cookie" with the cookie parsed at step 2
Set the header Content-Type: "application/x-www-form-urlencoded"
Set the header Content-Length with the length of the data
Send the POST with the data from step 4
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