I hope I’m not alone on this but I find the cURL documentation hard to follow and short on examples.
My goal was to mimic some HTTP XML posting traffic a server gets from IoT devices. Google Chrome Postman (or Postman REST Client) reproduction is very easy and will send.
TL;DR
- ensure you have an empty
--header "Content-Type:"
header: this ensures thatcURL
doesn’t add one and does not mess on how the content is being transferred.- use the
--data
or--data-binary
command with an @ to post a file as body.- if you want
--write-out
then be sure you have a recent cURL version.
This is how the IoT or Postman will send.
- Post headers like these:
Host:127.0.0.1:8080
Content-Length: 245
Connection:Keep-Alive
- Content like this:
<?xml version="1.0"?> | |
<Root Attribute="value"> | |
<Branch> | |
<Leaf>content</Leaf> | |
</Branch> | |
<Branch Attribute="value"> | |
<Bough Attribute="value"> | |
<Twig Attribute="value"> | |
<Leaf Attribute="value"/> | |
</Twig> | |
</Bough> | |
</Branch> | |
</Root> |
The data is being streamed to the HTTP server even with the very limited set of headers.
I’ve been unable to come up with exact cURL statement that exactly matches the headers and way the content is being transferred.
This is what I tried (in all examples, %1
is the IPv4 address of the HTTP 1.1 server):
POST
with the all the headers and the--data
command:
curl --request POST --header "Host: %1:8080" --header "Content-Length: 245" --header "Connection: Keep-Alive" --data @httpPostSample.xml http://%1:8080/target
This will hang the connection: somehow cURL will never notify the upload is done and the HTTP server keeps waiting. When you put --verbose
or --trace-ascii -
on the command-line you will see something like this before hanging: * upload completely sent off: 245 out of 245 bytes
.
Note the trick to emit the ASCII trace to
stdout
using--trace-ascii
with the minus sign: thanks to [WayBack] Daniel Stenberg for answering [WayBack] How can I see the request headers made by curl when sending a request to the server? – Stack Overflow.You can do the same with
--trace
which dumps all characters (not only ASCII) including their HEX representation
POST
with the all but theContent-Length
headers and the--data
command:
curl --request POST --header "Host: %1:8080" --header "Connection: Keep-Alive" --data @httpPostSample.xml http://%1:8080/target
This will automatically add a Content-Length: 245
header and complete the transfer. But it will also add a Content-Type: application/x-www-form-urlencoded
header causing the content not being posted as a body.
POST
with a--form file=
command:
curl --request POST --header "Host: %1:8080" --header "Connection: Keep-Alive" --form file=@httpPostSample.xml http://%1:8080/target
This will automatically ad a Content-Length: xxx
header (way longer than 245) because it converts the request into a Content-Type: multipart/form-data; boundary=------------------------e1c0d47bac806954
one (the hex at the end differs) which is totally unlike what Postman does.
It is also unlike to what the HTTP server accepts.
POST
with the all but theContent-Length
headers and the--data-binary
or--data-ascii
command as suggested on Stack Overflow :
curl --request POST --header "Host: %1:8080" --header "Connection: Keep-Alive" --data-binary @httpPostSample.xml http://%1:8080/target
curl –request POST –header “Host: %1:8080” –header “Connection: Keep-Alive” –data-binary @httpPostSample.xml http://%1:8080/target
It turns out that --data-ascii
is exactly the same as --data
and that --data-binary
just skips some new-line conversion when compared to --data
or --data-ascii
. Contrary to the --data-raw
documentation that suggest it is equivalent to --data-binary
it seems --data-raw
behaves exactly like --data
and --data-ascii
. Odd.
So these are all stuck with the Content-Type: application/x-www-form-urlencoded
and I thought I was running out of options.
Then I found [WayBack] soundmonster had posted an answer at [WayBack] http – What is the cURL command-line syntax to do a POST request? – Super User mentioning to add a Content-Type
header.
So I changed the request to include the --header "Content-Type: text/xml; charset=UTF-8"
header:
curl --request POST --header "Content-Type: text/xml; charset=UTF-8" --header "Host: %1:8080" --header "Connection: Keep-Alive" --data @httpPostSample.xml http://%1:8080/target
This works. But: the Content-Type
header is not present in the original request.
Finally it occurred to me: What if cURL would not insert a Content-Type
header if I add an empty Content-Type
header?.
That works!
curl --request POST --header "Content-Type:" --header "Host: %1:8080" --header "Connection: Keep-Alive" --data @httpPostSample.xml http://%1:8080/target
It posts exactly the same content as the IoT devices and Postman do.
Phew!
I tried to combine this with the --write-out
(a.k.a. -w
) option, but for older versions of cURL (I could reproduce with 7.34) that forces cURL back in to Content-Type: application/x-www-form-urlencoded
mode so watch your cURL version!
Later I will put more research in chuncked transfer. Links that might help me:
- [WayBack] Curl: How to force chunked upload?
- [WayBack] curl – Most straightforward way of getting a raw, unparsed HTTPS response – Unix & Linux Stack Exchange
- [WayBack] Curl: Re: HTTP PUT and chunked transfer
- [WayBack] Curl: Handling of chunked encoding
- [WayBack] php – curl error 18 – transfer closed with outstanding read data remaining – Stack Overflow
- [WayBack] POST file using Curl with chunked encoding
–jeroen
Some of the references:
- [WayBack] https://curl.haxx.se/docs/httpscripting.html
- [WayBack] https://curl.haxx.se/docs/manpage.html
- [WayBack] Curl: Re: verbose dataoutput for request-payload
- [WayBack] curl – How To Use
- [WayBack] How can I see the request headers made by curl when sending a request to the server? – Stack Overflow