-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for "Expect: 100-continue" header #679
Add support for "Expect: 100-continue" header #679
Conversation
This is a follow up to issue #676 |
567e647
to
e2c7fee
Compare
Thanks for looking into this! Overall looks like good changes. You can use the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments.
Was thinking of a bigger refactor might be clearer.
What if the part that just reads the header returns a new type ResponseHead
(not public). Then there are two methods to "complete" that type into the public Response
, either without_body
(for expect-100) and with_body
for the normal case?
It would potentially be cleaner than trying to make Response
be slightly different depending...
e2c7fee
to
bd0fa29
Compare
Thanks so much for the PR! I'll review it soon. |
Oops, I posted that before I saw @algesten had already provided a review! But I will still plan to find some time and take a look soon. |
bd0fa29
to
20bc250
Compare
I removed the "consume" argument from "read_response_head", the reason why it existed was because I was testing against a broken http server that for some strange reason responded with "100 Continue" twice... It took me embarassly long until I realised this and found that out in wireshark. I have now tested this code against a libsoup2 server and a python flask http server, works fine there. |
I think this looks pretty good. Wonder if @jsha agrees? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also looks good to me! Thanks so much for writing it.
Looks like you've got a clippy error. Mind fixing that and I'll merge? |
20bc250
to
046df14
Compare
Done! |
Also, regarding the broken client. I'm not sure if this is a flask bug or something. To be more exact, when using "default" flask it's broken, but when using flask with werkzeug "serving.make_server" it works fine. Might be a bug in flask? testclient fn request() {
let string = "abcde ".repeat(5);
let req = ureq::post("http://127.0.0.1:5000/")
.set("Expect", "100-continue");
let res = req.send(string.as_bytes()).unwrap();
println!("res2: {:?}", res);
assert_eq!(res.status(), 200);
let body = res.into_string().unwrap();
assert_eq!("Hello, world!", body);
}
fn main() {
request();
}
#[test]
fn test_a() {
for i in 1..1000 {
println!("\nRun {i}\n");
request();
}
} Flask with werkzeug serving.make_server (works) #!/usr/bin/env python3
from flask import Flask, request
from werkzeug import serving
app = Flask(__name__)
@app.route('/', methods = ['POST'])
def a():
return "Hello, world!"
if __name__=='__main__':
app.use_reloader = False
server = serving.make_server(
host="0.0.0.0",
port=5000,
app=app,
ssl_context=None)
server.serve_forever() Flask with app.run (broken) #!/usr/bin/env python3
from flask import Flask, request
app = Flask(__name__)
@app.route('/', methods = ['POST'])
def a():
return "Hello, world!"
if __name__=='__main__':
app.run() Wireshark of broken server
What happens is that because we get two 100-continue, our ureq client consumes the first 100-continue as it should and then puts the second 100-continue as its body, which the test client then asserts on as it expects a 200 OK. |
I might have dreamt this. But I seem to recall curl waits for a while before sending the body regardless of a 100 response. This is to interoperate with servers that doesn't understand expect-100 |
Ah yes |
Sounds reasonable, the only risk I see with this is if the server is just slow with responding with the 100-continue. So it might be a bit racy, seems like it would be important in that case to be able to configure the timeout? |
I think there are two things we want to consider.
|
I assume the racy-ness is normal behavior. The server would need to handle the case where the client starts sending the body regardless of the answer. In that respect 417 is only best effort.
Agree. We should put it as an option in |
I think the racy-ness can be resolved like so: If the client timed out waiting for the Here's the most up-to-date RFC (substantively the same as RFC 7231 on this issue): https://www.rfc-editor.org/rfc/rfc9110#field.expect
I think we're seeing that the "time out if no early response" SHOULD is actually pretty necessary for decent operation, so let's add it. @algesten any thoughts on wanting to include it as part of this PR or as a follow-on? I think the "retry on 417" SHOULD is good but I think it's not as urgent. |
Splitting those apart.
We should definitely follow this. No expect unless there's a body.
✅ Doing that already.
I think we should follow curl behavior here. Have a 1000ms default timeout and make it configurable on
Same as previous. But yeah. We shouldn't wait forever, even if it's not a MUST.
I think we should be good citizens and do this too, even if it's not a MUST. However on this point I can be convinced otherwise. Doesn't seem like a big deal though. Whether the work is added on to this PR or a new PR, I don't think matters much. Either way I think we need to do this before shipping a new version with this functionality. Just throwing out there: Should we do like curl and always send expect-100 headers and wait 1000ms when we have content? @johan-bjareholt You already done a lot here. Thanks! It's up to you whether you want to take on these further requirements. Let us know if you intend to continue, or we will work out a plan for the rest of the work. No pressure! |
The pro of not merging it would be that then there wouldn't be a hurry to fix it before the next release, as it doesn't become a blocker. If we don't find it to be complete enough for a release, maybe we shouldn't merge it?
To do it unconditionally seems like it would be against the point of what "Expect: 100-continue" is supposed to solve, possibility to deny requests early that are large to not waste network bandwidth and processing? To do it if the request payload is bigger than X megabytes would seem reasonable to me however.
Thanks, I highly appreciate the fast and thorough support! I would love to help out some more, but the coming week will be a bit busy for me. So if you have some patience, I could continue working on this. For my use-case, the current solution works but the suggested improvements would also help. |
Good point. I agree.
Yeah, I agree here too. Here's what the curl docs say:
I like this idea. We could do this for the known-length send methods like send_bytes. For send we won't know if the body will be miniscule or not. We could assume that
We're happy to wait. Let's say if you come back to in the next few weeks, great; if not we'll pick up your PR and run with it. Thanks! |
I have unfortunately been swamped the past few weeks so have not been able to work on this. Neither do I see myself having time to start working on the rest before the middle of february. If someone else has time to look at it, that'd be much appreciated. |
Since this is unfortunately taking so long, would it be an option to merge this and create a new issue for the things missing? |
046df14
to
2103584
Compare
Tried to rebase to fix merge conflict, but tests are failing on main again. Pushed fix for main in seperate PR #742 |
There does not seem to be any good reason to take ownership of it. Makes us able to remove a clone and a TODO comment.
b5244bd
to
0daee34
Compare
0daee34
to
c8e731b
Compare
468ff94
to
31ba284
Compare
Added some unit tests, implemented support for handling 417 according to spec and added a TODO in the PR description. |
09c739a
to
5419ffd
Compare
match response.status() { | ||
100 => debug!("Got 100-continue, proceeding with body"), | ||
200 => { | ||
// TODO: How should we handle this case? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any idea how to best handle this case?
If a server does not understand the "Expect: 100-continue" header, it will wait for the body indefinitely. To solve this issue, we add a shorter timeout on reading the response status+headers and if that timeout is hit we send the body anyway.
5419ffd
to
d54a264
Compare
@johan-bjareholt I haven't forgotten. Just been a lot lately. |
Sorry for nagging, just wanted to ping this again. |
I'm not ignoring this. I'm stalling! 🙈 |
To elaborate: ureq is now a quite popular crate, and I'm largely alone in maintaining it. This PR changes some of the inner workings and I've increasingly become more and more hesitant to do such things (don't know if you saw the fallout from trying to fix our test cases with Which isn't to say we don't want it, but I have this inertia/emotional block to get over. |
I understand. |
The ureq 3.x rewrite which now is in main should support expect-100-continue. Sorry for messing you around on this PR. |
It's ok, what's most important is that it's now supported. Thanks! |
Works well, very easy to use by just setting the expect header like this
To-do