I'm trying to send a Multipart request to a Web API using HttpWebRequest. The request I'm sending has the following format:
----------636194206488346738
Content-Disposition: form-data; name="file"; filename="A.png"
Content-Type:application/octet-stream
Content-Transfer-Encoding: binary
{Binary data in here}
----------636194206488346738--
{new line at the end}
And the request configuration is as follows:
Content-Type:"multipart/form-data; boundary=----------636194206488346738
Method: POST
Keep-Alive: True
When sending the request to the web API I get the Invalid end of stream error. However, I tried to convert the stream to text to see the actual data and it matches the example I added above.
However, when I'm using the WebClient and call the UploadFile method for the same purpose, I can successfully upload files to the API without any problem suggesting that something is wrong with my approach which is as follows.
My Constants:
Boundary = DateTime.Now.Ticks.ToString();
ContentType = "multipart/form-data; boundary=" + BoundaryDelimiter + Boundary;
BeginContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + Boundary + "\r\n");
EndContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + Boundary + "--\r\n");
Method for formatting form data:
private Byte[] FormDataFormat(String name, String fileName, String contentType)
=> System.Text.Encoding.UTF8.GetBytes(String.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type:{2}\r\nContent-Transfer-Encoding: binary\r\n\r\n", name, fileName, contentType));
Attaching file to a stream:
Stream = new MemoryStream();
foreach (var i in files) {
var tempEncode = FormDataFormat("file", i, "application/octet-stream");
var file = System.IO.File.ReadAllBytes(i); // Files are supposed to be small.
Stream.Write(BeginContent, 0, BeginContent.Length);
Stream.Write(tempEncode, 0, tempEncode.Length);
Stream.Write(file, 0, file.Length);
ContentLenght += BeginContent.Length + tempEncode.Length + file.Length;
}
Stream.Write(EndContent, 0, EndContent.Length);
ContentLenght += EndContent.Length;
Creating the request:
public HttpWebRequest Request(String method) {
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.ContentType = ContentType;
request.KeepAlive = true;
request.Method = method;
request.ContentLength = ContentLenght;
Stream.Seek(0, SeekOrigin.Begin);
Stream.CopyTo(request.GetRequestStream());
Stream.Dispose();
return request;
}
You haven't shown in your question the BoundaryDelimiter variable declaration but for the purpose of this answer I will assume that it is defined like this:
BoundaryDelimiter = "---------------------"
Now the problem with your code is that you are missing a couple of dashes (--) when calculating your boundary delimiters in order to conform to RFC2388.
So instead of:
BeginContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + Boundary + "\r\n");
EndContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + Boundary + "--\r\n");
You need this:
BeginContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + "--" + Boundary + "\r\n");
EndContent = System.Text.Encoding.UTF8.GetBytes("\r\n" + BoundaryDelimiter + "--" + Boundary + "--\r\n");
Notice the additional -- that I have added to your begin and end content boundary delimiters.
Check the example provided here:
Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="submit-name"
Larry
--AaB03x
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--AaB03x--
Notice how the boundary is equal to AaB03x but the delimiter is actually --AaB03x and of course the end delimiter is --AaB03x--.
As a side note you could get rid of the ContentLenght variable (which you have misspelled by the way :-)) and remove this line:
request.ContentLength = ContentLenght
This will simplify your code as the HttpWebRequest class will automatically populate the Content-Length header depending on the number of bytes you have written to the request stream.
This being said, I would recommend you using the HttpClient class instead of HttpWebRequest because it already has built-in capabilities for properly encoding this kind of stuff. It will definitely make your code more readable as you will not have to worry about boundaries, delimiters and content lengths.
Related
I have a script that is submitting a POST request of a form via AJAX.
When I look at the network tab, it is coming back in the format below, which I cannot read with a standard Request.Form in Classic ASP. I am seeing this server variable added to the page request as well due to the AJAX request: HTTP_X_REQUESTED_WITH = XMLHttpRequest
The form is set up as a simple POST: <form method="post" action="/contact-submit">
I cannot change the script performing the AJAX request to update the content type, etc.
This is the “Request payload” in the response on the network tab below. I have googled for days and cannot figure this out. How do you access this data? I even tried reading it with a file upload script I have via ASPUpload, but Upload.Form("contact_name") does not work either, it's blank as well.
I tried a simple PHP script (I do not know how PHP works, but this script came with script performing the POST as a demo), and calling print_r($_POST) the script passes all the correct info in an array back in the response on network tab. What the heck!!
Does anyone know how to get this data back in Classic ASP?
Thanks so much for the help in advance.
Dennis
-----------------------------254001430938683980861095915686
Content-Disposition: form-data; name="contact_name"
Test Name
-----------------------------254001430938683980861095915686
Content-Disposition: form-data; name="contact_email"
test#test.com
-----------------------------254001430938683980861095915686
Content-Disposition: form-data; name="contact_message"
my message
-----------------------------254001430938683980861095915686--
I worked on a solution to reading the data, this works below. Not sure it is the best / least expensive way to do this, but it works!
Thanks to everyone for the help / tips. If anyone has a better way to parse the response above, I'm all ears :)
<%
Function BytesToStr(bytes)
Dim Stream
Set Stream = Server.CreateObject("Adodb.Stream")
Stream.Type = 1 'adTypeBinary
Stream.Open
Stream.Write bytes
Stream.Position = 0
Stream.Type = 2 'adTypeText
Stream.Charset = "iso-8859-1"
BytesToStr = Stream.ReadText
Stream.Close
Set Stream = Nothing
End Function
If Request.TotalBytes > 0 Then
Dim lngBytesCount, post
lngBytesCount = Request.TotalBytes
post = BytesToStr(Request.BinaryRead(lngBytesCount))
End If
Response.ContentType = "text/plain"
sContentType = Replace(Request.ServerVariables("CONTENT_TYPE"),"multipart/form-data; boundary=---------------------------","")
arrPost = Split(post,"-----------------------------" & sContentType)
For i = 0 to UBound(arrPost)
sVal = Replace(arrPost(i),"Content-Disposition: form-data; name=","")
arrVal = Split(sVal,Chr(10),1)
For a = 0 to UBound(arrVal)
If Instr(1, arrVal(a), "contact_name") <> 0 Then
Response.Write GetValue(arrVal(a), "contact_name")
End If
If Instr(1, arrVal(a), "contact_message") <> 0 Then
Response.Write GetValue(arrVal(a), "contact_message")
End If
Next
Next
Function GetValue(f_FullString, f_FieldName)
fieldval = Replace(f_FullString, """" & f_FieldName & """","")
'response.Write "_" & fieldval & "_<Br>"
arrVal1 = Split(fieldval,Chr(10),1)
For b = 0 to UBound(arrVal1)
newval = arrVal1(b)
newval = Left(newval,Len(newval) - 2)
newval = Right(newval,Len(newval) - 6)
'For z = 1 to Len(newval)
' CurrChar = Mid(newval, z, 1)
' Response.Write Asc(CurrChar) & "<bR>"
'Next
Next
GetValue = newval
End Function
%>
UPDATE:
This is another solution if you have ASPUpload installed. I tried this last night, but forgot to add Upload.Save (which would have saved me 4hours of work --- UGGGGGHHHH).
'http://www.aspupload.com/manual_memory.html
Set Upload = Server.CreateObject("Persits.Upload")
Upload.Save
Name = Upload.Form("contact_name")
Response.Write Name
I am trying to send image through API in Micropython. still no solution how to do it. please help
import urequests
import json
URL = 'https://example.com/test'
datas = json.dumps({"auth_key": "43435", "mac": "abcd", "name": "washid"})
filep = 'OBJ.jpg'
filess = {'odimg': open(filep, 'rb')}
try:
response = urequests.post(URL,data=datas,files=files)
print(response.json())
except Exception as e:
print(e)
Maybe this template can help you :
import ubinascii
import uos
import urequests
def make_request(data, image=None):
boundary = ubinascii.hexlify(uos.urandom(16)).decode('ascii')
def encode_field(field_name):
return (
b'--%s' % boundary,
b'Content-Disposition: form-data; name="%s"' % field_name,
b'',
b'%s'% data[field_name]
)
def encode_file(field_name):
filename = 'latest.jpeg'
return (
b'--%s' % boundary,
b'Content-Disposition: form-data; name="%s"; filename="%s"' % (
field_name, filename),
b'',
image
)
lines = []
for name in data:
lines.extend(encode_field(name))
if image:
lines.extend(encode_file('file'))
lines.extend((b'--%s--' % boundary, b''))
body = b'\r\n'.join(lines)
headers = {
'content-type': 'multipart/form-data; boundary=' + boundary,
'content-length': str(len(body))}
return body, headers
def upload_image(url, headers, data):
http_response = urequests.post(
url,
headers=headers,
data=data
)
if http_response.status_code == 204:
print('Uploaded request')
else:
raise UploadError(http_response)
http_response.close()
return http_response
You need to declare an header for your request
I used Jonathan's answer. Had to modify the code a bit (-thanks to Akshay to figure this out). A fixed boundary is used instead of generating a new one every time. Also, there needs to be an additional \r\n at the end of the file. I have used this to upload photos to Telegram Bot using ESP-32 CAM.
def make_request(data, image=None):
boundary = '---011000010111000001101001'
#boundary fixed instead of generating new one everytime
def encode_field(field_name): # prepares lines that include chat_id
return (
b'--%s' % boundary,
b'Content-Disposition: form-data; name="%s"' % field_name,
b'',
b'%s'% data[field_name] #field_name conatains chat_id
)
def encode_file(field_name): # prepares lines for the file
filename = 'latest.jpg' # dummy name is assigned to uploaded file
return (
b'--%s' % boundary,
b'Content-Disposition: form-data; name="%s"; filename="%s"' % (
field_name, filename),
b'',
image
)
lines = [] # empty array initiated
for name in data:
lines.extend(encode_field(name)) # adding lines (data)
if image:
lines.extend(encode_file('photo')) # adding lines image
lines.extend((b'--%s--' % boundary, b'')) # ending with boundary
body = b'\r\n'.join(lines) # joining all lines constitues body
body = body + b'\r\n' # extra addtion at the end of file
headers = {
'content-type': 'multipart/form-data; boundary=' + boundary
} # removed content length parameter
return body, headers # body contains the assembled upload package
def upload_image(url, headers, data):
http_response = urequests.post(
url,
headers=headers,
data=data
)
print(http_response.status_code) # response status code is the output for request made
if (http_response.status_code == 204 or http_response.status_code == 200):
print('Uploaded request')
else:
print('cant upload')
#raise UploadError(http_response) line commneted out
http_response.close()
return http_response
# funtion below is used to set up the file / photo to upload
def send_my_photo(photo_pathstring): # path and filename combined
token = 'authentication token or other data' # this my bot token
chat_id= 999999999 # my chat_id
url = 'https://api.telegram.org/bot' + token
path = photo_pathstring # this is the local path
myphoto = open(path , 'rb') #myphoto is the photo to send
myphoto_data = myphoto.read() # generate file in bytes
data = { 'chat_id' : 999999999 }
body, headers = make_request(data, myphoto_data) # generate body to upload
url = url + '/sendPhoto'
headers = { 'content-type': "multipart/form-data; boundary=---011000010111000001101001" }
upload_image(url, headers, body) # using function to upload to telegram
I have a question about the SAP Function Module "http_post".
I just want to post a short message (msg) out of the SAP to a Push Notification Server (pushd-Github-Projekt) I installed before. Now I'm not sure how to pass the message.
I tested the FM with the test-symbol:
CALL FUNCTION 'HTTP_POST'
exporting
ABSOLUTE_URI = uri " Uniform Resource Identifier (RFC 1945)
* REQUEST_ENTITY_BODY_LENGTH = 14 "request_entity_body_length
* RFC_DESTINATION = " RFC Destination
* PROXY = " HTTP Proxy Rechner
* PROXY_USER = " Benutzername auf dem Proxy Rechner
* PROXY_PASSWORD = " Passwort auf dem Proxy Rechner
* USER = " Benutzername auf dem HTTP Server
* PASSWORD = " Passwort auf dem HTTP Server
* BLANKSTOCRLF = " Blanks in CRLF konvertieren im Entity Body
* importing
* STATUS_CODE = " Statuscode ( 2xx = OK )
* STATUS_TEXT = " Text zum Statuscode
* RESPONSE_ENTITY_BODY_LENGTH = " Länge vom Response-Entity-Body
tables
REQUEST_ENTITY_BODY = '{"msg":"test"}' "request_entity_body
RESPONSE_ENTITY_BODY = '' " Response-Entity-Body Daten
RESPONSE_HEADERS = '' " Header Zeilen vom Response
REQUEST_HEADERS = 'Content-Type: application/json' "request_headers
* exceptions
* CONNECT_FAILED = 1
* TIMEOUT = 2
* INTERNAL_ERROR = 3
* TCPIP_ERROR = 4
* SYSTEM_FAILURE = 5
* COMMUNICATION_FAILURE = 6
* OTHERS = 7
.
I know my values are no tables, but I tested it with the test-symbol, where you can write the values directly in a table.
When I start the FM, I get an Bad Request error in the SAP
and this error at the push notification server:
SyntaxError: Unexpected token
at Object.parse (native)
at IncomingMessage.<anonymous> ...Path from the pushd..
express\node_modules\connect\lib\middleware\json.js:76:27
at incomingMessage.EventEmitter.emit events.js:92:17
at _stream:readable.js:919:16
at process._tickCallback <node.js:419:13>
Can anyone help me how to pass the request to the FM HTTP-Post? It has to be sth. with msg, because otherwise the Push-Notification-Server can't handle it.
In SAP_BASIS release 731 or higher, I would strongly recommend to use the class CL_HTTP_CLIENTfor doing HTTP requests. See here an example report on how to do it. Replace the dummy string http:1.2.3.4:80/testjon/by your URL in question.
report z_test_http_post.
start-of-selection.
perform start.
* ---
form start.
data: lv_status type i,
lv_error_occurred type flag,
lv_error_msg type string,
lv_response_body type string.
perform send_json using
'http://1.2.3.4:80/testjson/' " Use your URL here
'{"hello":"world"}' " Use your JSON here
changing lv_status lv_response_body
lv_error_occurred
lv_error_msg.
* Show result
format color col_heading.
write: / 'Response status:', lv_status.
if lv_error_occurred = 'X'.
format color col_negative.
write: / 'Error occurred:', lv_error_msg.
endif.
format color col_normal.
write: / 'Response:', lv_response_body.
endform. "start
form send_json using iv_url type string
iv_json_data type string
changing cv_status type i
cv_response_body type string
cv_error_occurred type flag
cv_error_msg type string.
data: lo_client type ref to if_http_client.
clear: cv_error_msg,
cv_status,
cv_error_occurred,
cv_error_msg.
if iv_url is initial.
* No URL passed
message e349(sbds) into cv_error_msg.
cv_error_occurred = 'X'.
return.
endif.
call method cl_http_client=>create_by_url
exporting
url = iv_url
importing
client = lo_client
exceptions
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
others = 4.
if sy-subrc ne 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
into cv_error_msg.
cv_error_occurred = 'X'.
return.
endif.
lo_client->request->set_cdata( iv_json_data ).
lo_client->request->set_content_type( 'application/json' ).
lo_client->request->set_method( 'POST' ).
call method lo_client->send
exceptions
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3
others = 4.
if sy-subrc ne 0.
lo_client->get_last_error( importing message = cv_error_msg ).
cv_error_occurred = 'X'.
return.
endif.
lo_client->receive( exceptions others = 1 ).
if sy-subrc ne 0.
lo_client->get_last_error( importing message = cv_error_msg ).
cv_error_occurred = 'X'.
return.
endif.
cv_response_body = lo_client->response->get_cdata( ).
lo_client->response->get_status( importing code = cv_status ).
endform.
How do I convert string or a value to a byte array?
I need it for SOAP authentication. My requirement is to -
On the client side, this is how to create a digest:
1. Client Side Digest Array = byte array nonce + byte array UTC date-time of UTF-8 string + byte array UTF-8 plain text password (concatenate these three).
2. Client Side SHA-1 Digest = Hash with SHA-1 algorithm the Client Side Digest Array.
3. Client Side WS-Security Digest = 64-bit encode the Client Side SHA-1 Digest
Password_Digest = Base64 ( SHA-1 ( nonce + timestamp + password ) )
This is the code I am using to generate nonce, timestamp and digest_password. User password is a string. Some ting is wrong in the whole process and my digest is not successfully generated. I guess I have these data types right, byte array and UTF8 is confusing me.
I added utf8 conversion but no difference.
def nonce
chars = ("a".."z").to_a + ("1".."9").to_a + ("A".."Z").to_a
#nonce = Array.new(20, '').collect{chars[rand(chars.size)]}.join
end
def timestamp
t = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%LZ")
#timestamp = t.to_s
end
def digest_password
ic = Iconv.new('UTF-8//IGNORE', 'US-ASCII')
$time = ic.iconv( timestamp + ' ')[0..-2]
$pass = ic.iconv( password + ' ')[0..-2]
temp = (nonce.bytes.to_a + $time.bytes.to_a + $pass.bytes.to_a)
#digest_password = Base64.strict_encode64(Digest::SHA1.hexdigest(temp.to_s))
### temp = Digest::SHA1.hexdigest(nonce + timestamp + password) ##old
####digest_password = Base64.encode64(temp) ##old
end
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wsdl="http://xml.myserver.com/ok/service/v1_5" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header>
<wsse:Security env:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-1" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>user</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">YWIwM2QyZWI3YTEwMTAzZmNkNmZiNmEwMjg1ODlkOTU0OTNmNmUxYQ==
</wsse:Password>
<wsse:Nonce>ZEUyQ2J6bmw5cjdDZmt1QjVqTjQ=</wsse:Nonce>
<wsu:Created>2012-03-27T11:08:35.125Z</wsu:Created>
</wsse:UsernameToken>
Finally was able to solve this issue.
Password_Digest = Base64 ( SHA-1 ( nonce + create + password) )
nonce = nonce as string. Before Base64 endoce, E.g "1234"
create = time as string. No encoding
password = password as string. No encoding.
Base64Nonce = Base64.encode64(nonce).strip #Base64 encode of "1234"
chars = ("a".."z").to_a + ("1".."9").to_a + ("A".."Z").to_a
nonce = Array.new(20, '').collect{chars[rand(chars.size)]}.join
t = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%LZ")
$time = t
$pass = p
Base64Nonce = Base64.encode64(nonce).strip
$digest_pass = Base64.encode64(Digest::SHA1.digest(nonce + $time + $pass)).strip
"wsse:Username" => username,
"wsse:Password" => $digest_pass,
"wsse:Nonce" => Base64Nonce,
"wsu:Created" => $time,
Is it right to Base64.encode in the nonce method and then Base64.encode again in the digest_password method? Seems strange for me. Maybe you should do
def nonce
chars = ("a".."z").to_a + ("1".."9").to_a + ("A".."Z").to_a
#nonce = Array.new(20, '').collect{chars[rand(chars.size)]}.join
end
#response = Typhoeus::Request.get(FOUR_SQUARE_API_SERVER_ADDRESS+'search?ll=' + current_user.altitude.to_s + "&query="+ params[:query] + FOUR_SQUARE_API_ACESS_CODE)
#venues = ActiveSupport::JSON.decode(#response.body)
#venues['response']['groups'][0]['items'].each do |venue|
venue['name'] //working
venue['name']['location'][0]['address'] //issues
venue['name']['categories'][0]['id'] //issues
end
Please check inline comments for issues.
First of all, the venue['name'] is a scalar, not an array; secondly, venue['location'] (which I think you're trying to access) is not encoded as an array, that's just an object:
location: {
address: "...',
city: "...",
// ...
}
So here you want:
venue['location']
Then, your venue['name']['categories'][0]['id'] will fail because, again, venue['name'] is a scalar; for this one, you want:
venue['categories'][0]['id']