yhirose / cpp-httplib

A C++ header-only HTTP/HTTPS server and client library
MIT License
12.76k stars 2.26k forks source link

POST with no POST data returns HTTP 400 #1738

Closed thedayofcondor closed 9 months ago

thedayofcondor commented 9 months ago

Hi,

I am using v0.14.2

If I do a POST without a body, eg: curl -X POST http://192.168.1.1:8080/download cpp-httplib waits for 5 seconds (presumably waiting to read the body) and then returns HTTP 400.

This is independent from the path having a handler (if a handler is attached to that path, the handler code is never called) or not (in which case I would expect HTTP 404).

yhirose commented 9 months ago

This is independent from the path having a handler (if a handler is attached to that path, the handler code is never called) or not (in which case I would expect HTTP 404).

I don't fully understand what you are saying with this statement. Could you explain more? Thanks!

thedayofcondor commented 9 months ago

Sorry, I mean, this is mt code srv.Post("/download", myHandler...)

Using curl -X POST http://192.168.1.1:8080/downloadand curl -X POST http://192.168.1.1:8080/downloadxxxxx - if I don't submit a body I get a 400 response after a 5s delay from both curls, independently from the fact /download is valid endpoint (but myHandler is never called) and /downloadxxxxx is not (and should return 404)

bugdea1er commented 9 months ago

Im getting the same with PUT and PATCH methods

GET, HEAD, OPTIONS and DELETE work fine

yhirose commented 9 months ago

@thedayofcondor in your case, does the request from curl contain the 'Content-Length' header? If so, what is the value?

thedayofcondor commented 9 months ago

Yes - you are correct, with an empty POST there is no content-length at all

$ curl -X POST http://localhost:8080

$ nc -l 8080| cat -v
POST / HTTP/1.1^M
Host: localhost:8080^M
User-Agent: curl/7.81.0^M
Accept: */*^M
^M

$ curl -X POST -d' ' http://localhost:8080

$ nc -l 8080| cat -v
POST / HTTP/1.1^M
Host: localhost:8080^M
User-Agent: curl/7.81.0^M
Accept: */*^M
Content-Length: 1^M
Content-Type: application/x-www-form-urlencoded^M
^M
yhirose commented 9 months ago

@thedayofcondor could you test with the latest httplib.h on the master branch? If it works on your machine, I'll publish the next version '0.14.3'. Thanks.

thedayofcondor commented 9 months ago

Thanks, I will test it on my next build and let you know!

yhirose commented 9 months ago

@thedayofcondor 0.14.3 is actually out already. Please let me know if you have any problem with it. Thanks!

slwwpy commented 8 months ago

@thedayofcondor 0.14.3 is actually out already. Please let me know if you have any problem with it. Thanks!

I've recently started playing around with this library and I'm pretty sure I'm still experiencing this issue (or a similar one) on 0.14.3 (and on master). Running curl with no post params or body causes it to hang for a short while before calling my Post handler.

So my callback is eventually run but it takes a couple of seconds. Works fine if I add a dummy post param in curl.

thedayofcondor commented 8 months ago

@slwwpy just today I migrated to 0.14.3

/install only has Get

$ time curl -X POST -I http://192.168.2.3:8080/install
HTTP/1.1 404 Not Found
Content-Length: 0
Keep-Alive: timeout=5, max=5

real    0m5.019s
user    0m0.001s
sys     0m0.014s

/download only has Post

$ time curl -X POST -I http://192.168.2.3:8080/download
HTTP/1.1 404 Not Found
Content-Length: 39
Content-Type: text/plain
Keep-Alive: timeout=5, max=5

real    0m5.021s
user    0m0.003s
sys     0m0.012s

/invalidurl is not defined at all

$ time curl -X POST -I http://192.168.2.3:8080/invalidurl
HTTP/1.1 404 Not Found
Content-Length: 0
Keep-Alive: timeout=5, max=5

real    0m5.013s
user    0m0.002s
sys     0m0.007s

In all 3 cases a POST without a body seems to take 5 seconds before timing out, so it do not think the behaviour has changed compared to 0.14.2.

Adding -d"" works as expected (explicitly stating empty body).

However, I am not sure how the HTTP standard says you have to handle those cases, assuming the client does not send a content-length=0 how is the web server supposed to know there is not going to be any post data?

slwwpy commented 8 months ago

Just briefly looking at this respons to an old curl issue (https://github.com/curl/curl/issues/2183#issuecomment-352903012), the "correct" way to use curl appears to be to provide -d"" rather than -XPOST since -d"" implies POST with empty content, while -X just forces curl to use that method.

This is what the HTTP spec has to say: https://www.rfc-editor.org/rfc/rfc9110#section-8.6-5

A user agent SHOULD send Content-Length in a request when the method defines a meaning for enclosed content and it is not sending Transfer-Encoding. For example, a user agent normally sends Content-Length in a POST request even when the value is 0 (indicating empty content). A user agent SHOULD NOT send a Content-Length header field when the request message does not contain content and the method semantics do not anticipate such data.

I interpret this that POST should always come with a content-length 0 when used correctly so this might be a non-issue.

thedayofcondor commented 8 months ago

@slwwpy agree - but then httplib should not wait for a body for 5 seconds, at least on endpoints without a Post?

How I read it - the specs say SHOULD - not MUST - setting content-length on a POST is recommended, not compulsory, so waiting is the right thing to do

However is not clear at what point httplib should return a 404? Do we need to read the POST body before determining we do not have anyone to send that data to?

yhirose commented 8 months ago

@yhirose my change at b4748a2 is not to return a response with 400 instead of 400, and the 'waiting 5 seconds' thing remains. It's because the request doesn't contain Content-Length and the server expects arbitrary length of data might come. Checking the reading time out is the only way for the server to recognize the incoming data ends. (You can change the reading time out with set_read_timeout method at runtime or CPPHTTPLIB_READ_TIMEOUT_SECOND at compile time. Hope it helps.