Stress testing URI using xargs + curls bash script failing with status empty - bash

I'm trying to do user acceptance testing on an application which becomes unresponsive on a particular URL parameter included in the GET request.
Steps
I have curl and run the GET req (crafted) copied curl syntax for Unix and copied to ubuntu server along with some changes.
'https://abc.ai/getMultiDashboard/demouser' -H 'Cookie: _ga=GA1.2.561275388.1601468723; _hjid=ecd3d778-b7f5-4f7f-b3ef-6f9f12b13d66; 54651cc_an=4; _gid=GA1.2.1366208807.1601560229; _hjTLDTest=1; 54651cc_data=JTdCJTIyaWQlMjIlM0Ellc3NUb2tlbiUyMiUzQSUyMjA2MTk3NjM3NTgwOGE2N2RmZjlhMmJlOWJmODE5NDQzJTIyJTdE; 54651cc_loggedin=1; 54651cc_sound=true; 54651cc_read=true; 54651cc_popup=true; 54651cc_disablelastseen=false; 54651cc_usertype=loginuser; _hjIncludedInPageviewSample=1; _hjAbsoluteSessionInProgress=0; abc=s%3A8ZGd7Mol31n_Y8OCLq39dHoo3_mIlRhZ.pFQWz5gG9McKsQLzOikcTBmmb2Wcrxo%2B9u9iPpqoyxw; pageUrl=/#/dashboard/18; _gat_gtag_UA_97985973_5=1'
"https://abc.ai/getTagTrends/E1_CPU_PERCENTAGE/2020-9-12%2013:4:0/202**'23548'**0-09-15|%2013:04:00"
"https://abc.ai/getTagTrends/E1_CPU_PERCENTAGE/2020-9-12%2013:4:0/202**'`23548`'**0-09-15|%2013:04:00"
The ** asterisks are not part of the actual values; I use them to demarcate my injected value
Using a small bash script I have generated 1000s of (unique) payload combinations for Curl.
#/bin/bash
for ((i=0; i<1000; ++i)); do
echo "
'https://abc.ai/getMultiDashboard/demouser' -H 'Cookie: _ga=GA1.2.561275388.1601468723; _hjid=ecd3d778-b7f5-4f7
f-b3ef-6f9f12b13d66; 54651cc_an=4; _gid=GA1.2.1366208807.1601560229; _hjTLDTest=1; 54651cc_data=JTdCJTIyaWQlMjIlM0ElMjJkZW1vdXNlciU yMiUyQyUyMm4lMjIlM0ElMjJkZW1vdXNlciUyMiUyQyUyMmZyaWVuZHMlMjIlM0ElMjIlMjIlMkMlMjJhdXRoJTIyJTNBJTIyZWQ0YjVhNDFkMzJlY2U4MzQ3Mzk0ZjlkZT U5YThjMWQlMjIlMkMlMjJyZWZlcmVyJTIyJTNBJTIyaXJpZGl1bS1wcmVwcm9kLmVtcGlyaWMuYWklMjIlMkMlMjJhY2Nlc3NUb2tlbiUyMiUzQSUyMjA2MTk3NjM3NTgwO
GE2N2RmZjlhMmJlOWJmODE5NDQzJTIyJTdE; 54651cc_loggedin=1; 54651cc_sound=true; 54651cc_read=true; 54651cc_popup=true; 54651cc_disable
lastseen=false; 54651cc_usertype=loginuser; _hjIncludedInPageviewSample=1; _hjAbsoluteSessionInProgress=0; abc=s%3A8ZGd7Mol31n_
Y8OCLq39dHoo3_mIlRhZ.pFQWz5gG9McKsQLzOikcTBmmb2Wcrxo%2B9u9iPpqoyxw; pageUrl=/#/dashboard/18; _gat_gtag_UA_97985973_5=1' \"https://abc.ai/getTagTrends/E1_CPU_PERCENTAGE/2020-9-12%2013:4:0/202'$((1 + RANDOM % 10000000))'0-09-15|%2013:04:00\""
> URL.txt
done
Final command for testing (one-liner) fails as
cat URL.txt | xargs -I{} -- curl -O {}
Output:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0
Expected output
when I run the curl manually copying the contents from URL file I get
[{"dashboard_id": 18, "user_id": "demouser", "dashboard_name": "My_dashboard_1", "description": "Test description One", "creation_date": "2020-09-21 10:13:00", "dashboard_config": null, "id": 5}]
<html>
<head><title>504 Gateway Time-out</title></head>
<body>
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>nginx/1.18.0</center>
In order to troubleshoot, i used set -x on shell cmd-line I can't see why or how the request is crafted and handled by the curl processes. The curl output shows output (above) which has all 0 values in all fields, this tell me its just a bad malformed request, which isn't the actual case since i manually tested running the URL payload given in URL.txt multiple times it works.
EMPTY LINE
CODE
NEW-LINE
CODE
NEWLINE
...
I want to generate as many parallel requests as possible, without waiting for the first one to finish.
Debug
running it with -v using one-liner (showing only importants lines)
> GET /getMultiDashboard/demouser -H Cookie: _ga=GA1.2.561275388.1601468723; _hjid=ecd3d778-b7f5-4f7f-b3ef-6f9f12b13d66; 54651cc_an=4; _hjTLDTest=1; 54651cc_data=JTdCJTIyaWQlMjIlM0ElMgwOGE2N2RmZjlhMmJlOWJmODE5NDQzJTIyJTdE; 54651cc_loggedin=1; 54651cc_sound=true; 54651cc_read=true; 54651cc_popup=true; 54651cc_disablelastseen=false; 54651cc_usertype=loginuser; _gid=GA1.2.1722546791.1601890062; _hjIncludedInPageviewSample=1; _hjAbsoluteSessionInProgress=0; abc=s%3AKsRWcfNnOkbDHh1e65C3NwiDSZMx4LYg.zxLIymu488Ii5Z2%2Brz0qiwS17BzK2P7A0OoTSCHlMQM; pageUrl=/ HTTP/1.1
> Host: abc.ai
> User-Agent: curl/7.58.0
> Accept: */*
>
{ [5 bytes data]
< HTTP/1.1 400 BAD_REQUEST
< Content-Length: 0
< Connection: Close
When I run it with curl alone not using xargs I get the correct output no 400 bad request
> Cookie: _ga=GA1.2.561275388.1601468723; _hjid=ecd3d778-b7f5-4f7f-b3ef-6f9f12b13d66; 54651cc_an=4; _hjTLDTest=1; 54651cc_data=JTdCJTIyaWQlMjIlM0ElMjJkZW1vdXNlciUyMiUyQyUyMm4lMjIlM0ElMjJkZW1vdXNJlOWJmODE5NDQzJTIyJTdE; 54651cc_loggedin=1; 54651cc_sound=true; 54651cc_read=true; 54651cc_popup=true; 54651cc_disablelastseen=false; 54651cc_usertype=loginuser; _gid=GA1.2.1722546791.1601890062; _hjIncludedInPageviewSample=1; _hjAbsoluteSessionInProgress=0; abc=s%3AKsRWcfNnOkbDHh1e65C3NwiDSZMx4LYg.zxLIymu488Ii5Z2%2Brz0qiwS17BzK2P7A0OoTSCHlMQM; pageUrl=/#/dashboard; _gat_gtag_UA_97985973_5=1
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Date: Mon, 05 Oct 2020 09:48:51 GMT
< ETag: W/"3b4-gP1vMAXMzUZy+pt7cwyOmQslPT8"
< Server: nginx/1.18.0
< Strict-Transport-Security: max-age=15552000; includeSubDomains
< Vary: Accept-Encoding
< X-Content-Type-Options: nosniff
< X-DNS-Prefetch-Control: off
< X-Download-Options: noopen
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Content-Length: 948
< Connection: keep-alive
<
* Connection #0 to host abc.ai left intact
[{"dashboard_id": 18, "user_id": "demouser", "dashboard_name": "My_dashboard_1", "description": "Test description One", "creation_date": "2020-09-21 10:13:00", "2020-08-12 09:08:00", "dashboard_config": {}, "sort_id": 4, "id": 2}, {"dashboard_id": 5}]* Found bundle for host abc.ai: 0x55836cf75a50 [can pipeline]
* Re-using existing connection! (#0) with host abc.ai
* Connected to abc.ai (52.86.136.249) port 443 (#0)
> GET /getTagTr/E1_CP/2020-9-12%2013:4:0/202'6368'0-09-15|%2013:04:00 HTTP/1.1
> Host: abc.ai
> User-Agent: curl/7.58.0
> Accept: */*
> Cookie: _ga=GA1.2.561275388.1601468723; _hjid=ecd3d778-b7f5-4f7f-b3ef-6f9f12b13d66; 54651cc_an=4; _hjTLDTest=1; 54651cc_data=JTdCJTIyaWQlMjIlM0ElMjJkZW1vdXNlciUyMiUyQyUyMmjM3NTgwOGE2N2RmZjlhMmJlOWJmODE5NDQzJTIyJTdE; 54651cc_loggedin=1; 54651cc_sound=true; 54651cc_read=true; 54651cc_popup=true; 54651cc_disablelastseen=false; 54651cc_usertype=loginuser; _gid=GA1.2.1722546791.1601890062; _hjIncludedInPageviewSample=1; _hjAbsoluteSessionInProgress=0; abc=s%3AKsRWcfNnOkbDHh1e65C3NwiDSZMx4LYg.zxLIymu488Ii5Z2%2Brz0qiwS17BzK2P7A0OoTSCHlMQM; pageUrl=/#/dashboard; _gat_gtag_UA_97985973_5=1

Having multiple curl arguments and options in the same file adds a complication which probably isn't worth working around. Basically,
echo "http://example.com -H 'X-Hello: Hello'" | xargs curl -O
passes the entire argument to echo as a single string to curl, which interprets it as the URL to fetch.
My suggestion would be to put the URL and any other arguments on the command line, and only store the -H option's argument in the file.
for ((i=0; i<1000; ++i)); do
curl -O http://example.com -H "$(sed "s/%|/%$((1 + RANDOM))|/" xm.cookiefile)"
done
and run 400 (or whatever) of these jobs in parallel, perhaps just as regular background processes, or maybe with xargs if you think it adds value. (Maybe also look at GNU parallel which simplifies some aspects of this.)
I took out the big modulo because it's not doing anything; $RANDOM produces integers in the range 0-32767 so if you need a much bigger number, maybe paste together multiple $RANDOM numbers, or maybe use a different random source.

Related

can't use curl to query neo4j

I am trying to use curl to query neo4j
curl -X POST -H Accept:application/json -H Content-Type:application/json -u neo4j:password -v http://localhost:7474/db/neo4j/tx/commit -d '{"statements":[{"statement":"MATCH (n) RETURN n"}]}'
gives me this response
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:7474...
* Connected to localhost (127.0.0.1) port 7474 (#0)
* Server auth using Basic with user 'neo4j'
> POST /db/neo4j/tx/commit HTTP/1.1
> Host: localhost:7474
> Authorization: Basic bmVvNGo6cGFzc3dvcmQ=
> User-Agent: curl/7.79.1
> Accept:application/json
> Content-Type:application/json
> Content-Length: 47
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Wed, 27 Jul 2022 09:13:35 GMT
< Access-Control-Allow-Origin: *
< Content-Type: application/json
< Content-Length: 120
<
{"results":[],"errors":[{"code":"Neo.ClientError.Request.InvalidFormat","message":"Could not parse the incoming JSON"}]}* Connection #0 to host localhost left intact
If anyone could help please
Should have mentioned I'm on windows. Apparently you have to escape those double quotes in the json
This works for me now:
curl -X POST -H Accept:application/json -H Content-Type:application/json -u neo4j:password -v http://localhost:7474/db/neo4j/tx/commit -d "{\"statements\":[{\"statement\":\"MATCH (n) RETURN n\"}]}"

Jenkins pipeline not finish tomcat war deploy

I want to deploy a war from Jenkins using Tomcat manager.
Here is what i am doing from command line :
curl -v -u user:pasword -T target/app.war "http://host:8180/manager/text/deploy?path=&update=true"
It takes a little bit time and works :
* Hostname was NOT found in DNS cache
* Trying ip...
* Connected to host.com (ip) port 8180 (#0)
* Server auth using Basic with user 'jenkins'
> PUT /manager/text/deploy?path=&update=true HTTP/1.1
> Authorization: Basic amVua2luczpLbzNEaUE=
> User-Agent: curl/7.38.0
> Host: host.com:8180
> Accept: */*
> Content-Length: 71682391
> Expect: 100-continue
>
< HTTP/1.1 100
* We are completely uploaded and fine
< HTTP/1.1 200
< Cache-Control: private
< Expires: Thu, 01 Jan 1970 00:00:00 UTC
< Content-Type: text/plain;charset=utf-8
< Transfer-Encoding: chunked
< Date: Tue, 14 Aug 2018 13:18:22 GMT
<
OK - Application déployée pour le chemin de contexte [/]
* Connection #0 to host host.com left intact
My problem is when i am execute this command in Jenkins Pipeline :
stage('Tomcat Deploy') {
sh "curl -v -u user:password -T app.war http://host:8180/manager/text/deploy?path=&update=true"
}
The curl command is not finished correctly :
+ curl -v -u jenkins:pass -T app.war http://host:8180/manager/text/deploy?path=
* Hostname was NOT found in DNS cache
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying ip...
* Connected to host.com (ip) port 8180 (#0)
* Server auth using Basic with user 'jenkins'
> PUT /manager/text/deploy?path= HTTP/1.1
> Authorization: Basic amVua2luczpLbzNEaUE=
> User-Agent: curl/7.38.0
> Host: host.com:8180
> Accept: */*
> Content-Length: 71682391
> Expect: 100-continue
>
< HTTP/1.1 100
} [data not shown]
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
And it goes to the next stage without waiting the curl deploy result. Is there any quick solution to fix that?
Yes the war was not uploaded. But i resolved my problem using --upload-file option. Now Jenkins wait for upload as attended. Thank you.
A quick(but very bad) fix for this would be to put a sleep for some time in your code.
sleep(30)
This will give curl commad time to submit/upload your jar to your target server. ** Dont rely on this in Production **

curl post audio data with rate limit

I am trying to post audio data with curl for a HTTP-API which allows to transmit/receive audio files.
First I tried this:
curl -vv --http1.0 -H "Content-Type: audio/basic" -H "Content-Length: 9999999" -H "Connection: Keep-Alive" -H "Cache-Control: no-cache" --data-binary #- 'http://IP/API-Endpoint.cgi'
This seems to work:
* Trying [IP]...
* TCP_NODELAY set
* Connected to [IP] ([IP]) port 80 (#0)
> POST /API-Endpoint.cgi HTTP/1.0
> Host: [IP]
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type: audio/basic
> Content-Length: 9999999
> Connection: Keep-Alive
> Cache-Control: no-cache
>
* upload completely sent off: 17456 out of 17456 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: text/plain
< Content-Length: 0
* HTTP/1.0 connection set to keep alive!
< Connection: keep-alive
< Date: Wed, 06 Jun 2018 19:38:37 GMT
< Server: lighttpd/1.4.45
But I can only hear the very last part of the Audio file. (The file has the correct audio format for the API: G.711 μ-law with 8000 Hz) My next guess is, that the audio gets transmitted too fast and has to be sent in real time to the API endpoint. So I tried the --limit-rate parameter of curl, which had no effect. Then I tried piping the data with a rate limit into curl:
cat myfile.wav | pv -L 10k | curl -vv --http1.0 -H "Content-Type: audio/basic" -H "Content-Length: 9999999" -H "Connection: Keep-Alive" -H "Cache-Control: no-cache" --data-binary #- 'http://IP/API-Endpoint.cgi'
but the result is always the same: I can only hear the last part of the audio file. It seems like curl is waiting for the piped input to complete and then sends the request as before.
Is there an option to post audio to a HTTP-API from bash in "real time"?
Update:
Without forcing HTTP 1.0 I get the following result:
curl -vv -H "Content-Type: audio/basic" --data-binary '#myfile.wav' 'http://[IP]/API-Endpoint.cgi'
* Trying [IP]...
* TCP_NODELAY set
* Connected to [IP] ([IP]) port 80 (#0)
> POST /API-Endpoint.cgi HTTP/1.1
> Host: [IP]
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type: audio/basic
> Content-Length: 15087
> Expect: 100-continue
>
< HTTP/1.1 417 Expectation Failed
< Content-Type: text/html
< Content-Length: 363
< Connection: close
< Date: Wed, 06 Jun 2018 20:34:22 GMT
< Server: lighttpd/1.4.45
<
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>417 - Expectation Failed</title>
</head>
<body>
<h1>417 - Expectation Failed</h1>
</body>
</html>
* Closing connection 0
with -H "Content-Length: 9999999" you say that your audio file is exactly 9999999 bytes long (roughly 10 megabytes), but curl reports that your file is 17456 bytes:
* upload completely sent off: 17456 out of 17456 bytes
(roughly 0.02 megabytes), so either your Content-Length header is wrong (that's my best guess), or the program feeding your audio file to curl is faulty, closing stdin prematurely.
either fix your Content-Length header, or fix the program feeding curl's stdin, hopefully that should send the entire file intact.
EDIT: oh, seems that server can't handle Expect: 100-continue, to disable that header, add the argument -H 'Expect:'
(an empty Expect header will make curl omit the header entirely, instead of sending the header empty)
... but to answer the question in the title, yeah that's the --limit-rate argument.

curl syntax in GET based HTTP logins

For practice purposes I decided to create a simple bruteforcing bash script, that I succesuly used to solve DWVA. I then moved to IoT - namely my old IP camera. This is my code as of now:
#!/bin/bash
if [ "${##}" != "2" ]; then
echo "<command><host><path>"
exit
fi
ip=$1
path=$2
for name in $(cat user.txt); do
for pass in $(cat passwords.txt); do
echo ${name}:${pass}
res="$(curl -si ${name}:${pass}#${ip}${path})"
check=$(echo "$res" | grep "HTTP/1.1 401 Unauthorised")
if [ "$check" != '' ]; then
tput setaf 1
echo "[FAILURE]"
tput sgr0
else
tput setaf 2
echo "[SUCCESS]"
tput sgr0
exit
fi
sleep .1
done;
done;
Despite obvious flaws - like reporting succes in case of network failure - it's as good as my 20 minutes coding jobs are. However, I can't seem to get the curl command syntax quite right. Camera in question is a simple Axis, running cramFS and a small scripting os. It's similar to a lot of publicly available cameras' login forms, like ones found here, here or here. A simple GET, yet I feel like I'm bashing my head against a wall. Any bit of ahint will be madly appreciated at this point.
I've taken the liberty to paste contents of first GET package:
AYGET /operator/basic.shtml?id=478 HTTP/1.1
Host: <target_host_ip>
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://<target_host_ip>/view/view.shtml?id=282&imagepath=%2Fmjpg%2Fvideo.mjpg&size=1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Authorization: Digest username="root", realm="AXIS_ACCC8E4A2177", nonce="w3PH7XVmBQA=32dd7cd6ab72e0142e2266eb2a68f59e92995033", uri="/operator/basic.shtml?id=478", algorithm=MD5, response="025664e1ba362ebbf9c108b1acbcae97", qop=auth, nc=00000001, cnonce="a7e04861c3634d3b"
Package sent in return is a simple, dry 401.
PS.: Any powers that be - feel free to remove the IPs if they violate anything. Also feel free to point out grammar/spelling etc. mistakes since C2 exam is coming.
It looks like those cameras don't simply use "Basic" HTTP auth with a base64 encoded username:password combo, but use digest authentication which involves a bit more.
Luckily, with cURL this just means you need to specify --digest on the command line to handle it properly.
Test the sequence of events yourself using:
curl --digest http://user:password#example.com/digest-url/
You should see something similar to:
* Trying example.com...
* Connected to example.com (x.x.x.x) port 80 (#0)
* Server auth using Digest with user 'admin'
> GET /view/viewer_index.shtml?id=1323 HTTP/1.1
> Host: example.com
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Wed, 08 Nov 1972 17:30:37 GMT
< Accept-Ranges: bytes
< Connection: close
< WWW-Authenticate: Digest realm="AXIS_MACADDR", nonce="00b035e7Y417961b2083fae7e4b2c4053e39ef8ba0b65b", stale=FALSE, qop="auth"
< WWW-Authenticate: Basic realm="AXIS_MACADDR"
< Content-Length: 189
< Content-Type: text/html; charset=ISO-8859-1
<
* Closing connection 0
* Issue another request to this URL: 'http://admin:admin2#example.com/view/viewer_index.shtml?id=1323'
* Server auth using Digest with user 'admin'
> GET /view/viewer_index.shtml?id=1323 HTTP/1.1
> Host: example.com
> Authorization: Digest username="admin", realm="AXIS_MACADDR", nonce="00b035e7Y417961b2083fae7e4b2c4053e39ef8ba0b65b", uri="/view/viewer_index.shtml?id=1323", cnonce="NWIxZmY1YzA3NmY3ODczMDA0MDg4MTUwZDdjZmE0NGI=", nc=00000001, qop=auth, response="3b03254ef43bc4590cb00ba32defeaff"
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Wed, 08 Nov 1972 17:30:37 GMT
< Accept-Ranges: bytes
< Connection: close
* Authentication problem. Ignoring this.
< WWW-Authenticate: Digest realm="AXIS_MACADDR", nonce="00b035e8Y8232884a74ee247fc1cc42cab0cdf59839b6f", stale=FALSE, qop="auth"
< WWW-Authenticate: Basic realm="AXIS_MACADDR"
< Content-Length: 189
< Content-Type: text/html; charset=ISO-8859-1
<

1 second latency for HTTP/1.1 100 Continue for ruby (puma, unicorn, webrick)

When I send a request to newly created rails application (with rails new) it works fast:
~ 冬 time curl -v -X POST --data key=value http://localhost:3000/ok
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 3000 (#0)
> POST /ok HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Length: 9
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 9 out of 9 bytes
< HTTP/1.1 200 OK
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< Content-Type: application/json; charset=utf-8
< ETag: W/"a29ee2b15c494311c52521766e44af56"
< Cache-Control: max-age=0, private, must-revalidate
< X-Request-Id: 99e6d922-dcd6-4e25-89a2-35fa735a401e
< X-Runtime: 0.003076
< Transfer-Encoding: chunked
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
{"status":"ok"}curl -v -X POST --data key=value http://localhost:3000/ok 0.00s user 0.00s system 23% cpu 0.014 total
However, if I send a file or multipart/form-data it always take 1 second extra (1.014 total):
~ 冬 time curl -v -X POST --form key=value http://localhost:3000/ok
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 3000 (#0)
> POST /ok HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Length: 143
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------8cda3c76e0dbb84a
>
* Done waiting for 100-continue
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< Content-Type: application/json; charset=utf-8
< ETag: W/"a29ee2b15c494311c52521766e44af56"
< Cache-Control: max-age=0, private, must-revalidate
< X-Request-Id: 13720420-bd08-44f8-8a98-aca57d70331a
< X-Runtime: 0.002236
< Transfer-Encoding: chunked
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
{"status":"ok"}curl -v -X POST --form key=value http://localhost:3000/ok 0.01s user 0.00s system 0% cpu 1.014 total
application_controller.rb:
class ApplicationController < ActionController::Base
# protect_from_forgery with: :exception
def ok
render json: {status: 'ok'}
end
end
routes.rb:
Rails.application.routes.draw do
post '/ok' => 'application#ok'
end
I tested this on several different linux machines and also, similar php code works without such delay.
Also, I tried to debug the puma server and found out that it freeze here:
# reactor.rb, line 29
while true
begin
ready = IO.select sockets, nil, nil, #sleep_for # waits one second here
< ... >
The sockets argument contains Puma::Client instance and I don't have a clue how an object of this class can be passed to IO.select.
Any ideas on how to eliminate one second waiting?
I believe the issue is actually with the benchmark tool (curl) rather than the Ruby applications.
It seems that curl takes more time to both prepare and send the multipart/mime (form) request.
I wrote a quick Rack application to confirm the issue. Place the following code in config.ru:
require 'json'
app = proc do |env|
req = Rack::Request.new(env)
s = req.params.to_json
[200, {"Content-Length": s.bytesize}, [s]]
end
run app
I ran the app using iodine with 8 threads:
$ iodine -t 8 -v
Testing with curl:
$ time curl -X POST --data key=value http://localhost:3000/
{"key":"value"}
real 0m0.064s
user 0m0.004s
sys 0m0.003s
$ time curl --form key=value http://localhost:3000/
{"key":"value"}
real 0m1.021s
user 0m0.004s
sys 0m0.004s
Issue exists when parsing the request.
Next, I removed the request parsing and simply sent back a static string (the new config.ru):
require 'json'
app = proc do |env|
# req = Rack::Request.new(env)
s = "GO!" # req.params.to_json
[200, {"Content-Length": s.bytesize}, [s]]
end
run app
Results:
$ time curl --form key=value http://localhost:3000/
GO!
real 0m1.019s
user 0m0.004s
sys 0m0.003s
$ time curl -X POST --data key=value http://localhost:3000/
GO!
real 0m0.012s
user 0m0.004s
sys 0m0.003s
In other words, even when Ruby performs no parsing of the data (the multipart/mime isn't parsed), the issue persists.
I went ahead and tested with google...:
$time curl -X POST --data key=value http://google.com/
...
real 0m0.090s
user 0m0.004s
sys 0m0.004s
$ time curl --form key=value http://google.com/
...
real 0m1.083s
user 0m0.004s
sys 0m0.004s
The issue is with curl which takes more time to both prepare and send the request.
This is due to cURL and the 100-continue timeout.
In short, curl waits up to 1 second for your server to respond to the 100 Continue prior to sending the POST:
time curl --form key=value http://google.com/
> real 0m1.067s
Vs:
time curl --expect100-timeout 0.001 --form key=value http://google.com/
> real 0m0.086s

Resources