-
Notifications
You must be signed in to change notification settings - Fork 73
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
RFC: Streaming response bodies #158
Comments
@toland and anyone else - please do comment. |
Patron was originally intended to have a simple API. I wrote it because I really didn't like The callback-based API just feels wrong. It directly messes with how we read and understand code. The term "callback spaghetti" isn't meant as a compliment 😄 The first and third examples both look good, but I would say that the first is more in keeping with the spirit with which I created Patron. This is just my opinion. Take it with an appropriately sized grain of salt. |
Actually, the need for that sess = Patron::Session.new
sess.streaming_get('/get') do |stream|
if stream.status != 200
error_body = stream.receive_body_and_close
raise "Invalid status #{stream.status} - #{error_body}"
end
stream.each do |body_chunk|
# do something with a body chunk
end
end I like this approach because the It seems like you might also want methods like |
And |
Ok, imagine we do this. How do we ensure the stream gets closed correctly? sess.streaming_get('/get') do |stream|
begin
if stream.status != 200
error_body = stream.receive_body
raise "Invalid status #{stream.status} - #{error_body}"
end
stream.each do |body_chunk|
# the fourth iteration of this will raise something
end
ensure
stream.close
end
end |
I am not sure I understand the question, so if what I am about to say seems out in left field, it probably is. The stream is closed within def streaming_get(url) do
stream = setup_stream(url)
begin
yield stream
ensure
stream.close
end
end (Please note that my Ruby is rusty, and this was done off the top of my head. Be kind 😄 ) Your example could then be simplified to: sess.streaming_get('/get') do |stream|
if stream.status != 200
error_body = stream.receive_body
raise "Invalid status #{stream.status} - #{error_body}"
end
stream.each do |body_chunk|
# the fourth iteration of this will raise something
end
end We will need to make sure that |
I came here to say the same thing, the error handling should be inside the method we provide IMO (in this case sess.streaming_get('/get', on_error: &my_error_proc) do |stream|
if stream.status != 200
error_body = stream.receive_body
raise "Invalid status #{stream.status} - #{error_body}"
end
stream.each do |body_chunk|
# the fourth iteration of this will raise something
end
end Where EDIT: Possibly we could also already put the error handling for non-200 responses in the |
This has (not-unexpectedly) again become relevant 😆 so I think I'll try a go at the following semantics:
@WJWH @toland I will mention you from a PR once I have something |
I started work on the streaming response bodies in https://github.com/toland/patron/tree/response-body-callback – using a Ruby callback to get the body data as it gets returned. However, the current API Patron has for this is not a good fit because the Response object is only returned once the
curl_easy_perform
has returned. In practice, this leads to a pattern like this:This in turn means that implementing streaming response bodies is very obtuse, because we only get to the status code of the response later. I can envision the following APIs instead:
We could stretch that paradigm even further out and use something like https://github.com/Tonkpils/celluloid-eventsource use pattern which is very similar to EventMachine, and is also known as "callback spaghetti":
Yet another option is going for a minimalist API akin to what Excon uses, which would reduce the calling code to this:
That is only possible if we remove the Response objects from the picture, but it is probably not dramatic because the code calling long-polled endpoints is going to end up looking very different from the code that reads eagerly.
When you are using a streaming response you don't want to buffer on the Ruby side - actually you probably want the opposite - no buffering at all and the Curl buffer size set to a minimum, so we cannot buffer until
curl_easy_perform
returns. Besides, we might be dealing with long-running responses here, which would mean that the curl_easy_perform continues until the process/thread quits.Aside from the fact that this needs a lot of rearchitecting of the C code - what are people's thoughts on these APIs and what would be a better fit for Patron? I am at loss - what I do know is that all of these are possible, but I rather double-check we all like this before I do this change which is going to span hundreds of lines of C.
The text was updated successfully, but these errors were encountered: