Skip to content
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

read timeout request option (part 2) #1022

Merged
merged 22 commits into from
Oct 17, 2019

Conversation

technoweenie
Copy link
Member

This extends #1003 by adding Faraday::RequestOptions#fetch_timeout to clean up the logic of #configure_request in the net/http adapter. Instead of checking for req[:timeout] before the more specific timeout settings in the adapter, this happens in #fetch_timeout.

If this pattern is okay, I'll update the other adapters to use #fetch_timeout, ensuring they all properly support the new read_timeout setting. I'm open to other ideas, or even just different names.

springerigor and others added 10 commits July 18, 2019 11:03
Right now `timeout` setting sets all the timeout values (`read`, `open` & `write` if available). To unify the API with `Net::HTTP` one (which has a dedicated method for `read_timeout` as well) I propose extending the `RequestOptions` by `read_timeout`.
This allows adapter internals and specs to treat request options as a 
plain hash.

opts[:connect_timeout] = open_timeout
opts[:connect_timeout] = sec
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubocop forced me to turn this into a guard check ^^^

@technoweenie
Copy link
Member Author

I updated the EMHttp and Excon adapters with positive results and one small issue: the internal adapter code treats the Faraday::RequestOptions as a hash with symbol keys (req[:timeout] for example).

Externally, end users should never be able to pass a plain request options hash. Faraday will handle this conversion. But, a lot of specs tend to pass in plain hashes. So, I moved Faraday::RequestOptions#fetch_timeout(type) to Faraday::Adapter#request_timeout(type, req) in b35f6a2.

That will let us clean the timeout option logic a bit, while still (mostly) treating the request options as a hash.

However, other Options classes have methods on them. Even RequestOptions#stream_response? exists, requiring the existential operator in places:

req = env[:request]
if req&.stream_response?
warn "Streaming downloads for #{self.class.name} are not yet " \
' implemented.'
req.on_data.call(resp.body, resp.body.bytesize)
end

Big Question Time: Are we cool with adding methods to the Options classes, or should we strive to move that logic to Faraday::Adapter or somewhere else?

Personally, I'd like to bring RequestOptions#fetch_timeout back, and fix the tests to use RequestOptions.new instead of {}... but only if we're sure it's not going to cause breakage for some folks.

@technoweenie technoweenie changed the title read timeout request option (part 2) WIP: read timeout request option (part 2) Sep 19, 2019
@iMacTia
Copy link
Member

iMacTia commented Sep 20, 2019

It really depends on what meaning we give to the option classes. They are simple Structs, not fully-fledged classes. In my mind, this means they should only be used to store values and provide getter/setters methods. Any other method or logic should NOT be defined there.

That said, I guess we're a bit borderline here.
The stream_response? method simply asks info about one of the stored properties, without manipulating the content of the Struct.
Same goes for request_timeout, although the implementation is less trivial.
Another solution would be to override the *_timeout getters in RequestOptions to default to timeout, that would probably fit better in the Struct definition

@technoweenie
Copy link
Member Author

technoweenie commented Sep 20, 2019

In my mind, this means they should only be used to store values and provide getter/setters methods. Any other method or logic should NOT be defined there.

I agree, to a point. I like the properties on some of the options classes that only operate on the config properties themselves. Examples: ProxyOptions, RequestOptions, and SSLOptions. I do think Env pushes that boundary, but that's on the chopping block for Faraday 2 ;)

Another solution would be to override the *_timeout getters in RequestOptions to default to timeout, that would probably fit better in the Struct definition

Possibly, but most of the options are accessed as symbol hash keys. A case block in def [](key) could do this:

module Faraday
  class RequestOptions # ...
    def [](key)
      key_sym = key.to_sym
      case key_sym
      when :read_timeout then super(:timeout) || super(key)
      else super(key)
      end
    end

Currently I'm leaning towards sticking with Faraday::Adapter#request_timeout, rather than putting more code into the Options classes. I think a method call in the Adapter is more obvious that something custom is happening, than a basic req[:read_timeout] hash access.

EDIT: Just realized I flipped my position from the last comment. Clearly I'm not too tied to any option (pun not intended). While thinking about this issue, I considered reverting the Options objects to plain hashes as a task for Faraday 2, which is why I'm now leaning towards Faraday::Adapter#request_timeout.

Also, the F2 wishlist (#953) includes "Remove Options structs in favor of properties on Client/Request/Response", so I clearly not thrilled with the current Options implementation.

@iMacTia
Copy link
Member

iMacTia commented Sep 20, 2019

Right, considering the access to properties through symbols, it seems like Faraday::Adapter#request_timeout is the most logical solution 😄

@technoweenie technoweenie changed the title WIP: read timeout request option (part 2) read timeout request option (part 2) Oct 14, 2019
olleolleolle
olleolleolle previously approved these changes Oct 15, 2019
Copy link
Member

@olleolleolle olleolleolle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of the change, and I've made tiny notes inline, sometimes as GitHub Suggestions, other times as text.

Hope this helps!

# @return [Integer, nil] Timeout duration in seconds, or nil if no timeout
# has been set.
def request_timeout(type, options)
key = TIMEOUT_KEYS[type]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use a key = TIMEOUT_KEYS.fetch(type) do ... end and raise the error in there?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(We can do that in a follow-up PR, no biggie.)

lib/faraday/adapter/em_http.rb Show resolved Hide resolved
lib/faraday/adapter/net_http.rb Show resolved Hide resolved
lib/faraday/adapter/net_http.rb Show resolved Hide resolved
spec/faraday/adapter/net_http_spec.rb Show resolved Hide resolved
spec/faraday/options/request_options_spec.rb Show resolved Hide resolved
olleolleolle
olleolleolle previously approved these changes Oct 17, 2019
Copy link
Member

@olleolleolle olleolleolle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

iMacTia
iMacTia previously approved these changes Oct 17, 2019
@technoweenie
Copy link
Member Author

Thanks for the reviews 👋

@technoweenie technoweenie merged commit 9e4fa74 into master Oct 17, 2019
@technoweenie technoweenie deleted the springerigor-read-timeout-request-option branch October 17, 2019 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants