Faraday: Automatically set @handler to Faraday.default_adapter if left unspecified.

Created on 22 Feb 2012  ·  35Comments  ·  Source: lostisland/faraday

Normally for our simple scripts we set Faraday.default_adapter once at the top however while working with Github who (no offense) loves redirects we have to use faraday_middleware (well not have to use) to follow redirects which requires us to do a block (I assume) I think it would be better if Faraday assumed the handler should be set to Faraday.default_adapater if it's not set with use in the block, this saves some coding and redundant code IMO.

refactoring

Most helpful comment

+1 to this. At the very least, an error that no adapter was specified, or better just use the default_adapter if nothing was specified.

All 35 comments

Not sure about this, but will let it open. The default stack is UrlEncoded + default_adapter. If you're defining your own stack then you should define it 100%, including the adapter. But it's true that I most often use the default_adapter, so it might as well be a default.

I was thinking about refactoring Faraday adapters, making them endpoints instead of middleware (as per #47). This feature might be a part of that change.

:+1: This bit me for the second time this year. You'd think I'd learn. Would be nice if it were fixed.

Compromise: Faraday.with_default_stack(), so that we pour souls just trying to use Faraday for the first time don't have to know anything about "the default stack" just to be able to follow redirects. Reasonable?

@jbrains Yes I realize this is user-unfriendly. We might make it so that a descriptive error is raised if you didn't mount an adapter, or if you mounted it in the wrong place.

I did this in my project:

        def faraday_with_default_adapter(base, &block)
          Faraday.new(base) { | connection |
            yield connection
            connection.adapter Faraday.default_adapter
          }
        end

I know you don't want to support every permutation, but it seems like almost everyone's going to do this eventually. :) If I knew how to add the UrlEncoded bit (I haven't bumped into that yet), then I'd add it here.

A lint-style check with descriptive error would certainly help. Thanks. Even so, a little pokayoke would help us even more.

I'll consider sending you a pull request in the future. Meantime, thanks.

This consumed some hours of work when I tried to make an upload and didn't specify the default adapter (which I assumed was already set, since it's the default one). I kept debugging and inspecting the response/request objects and there was no problem at all, except I was missing the dreaded default adapter line.

I think this should at least be documented on the README somewhere or at least warn me that I needed an explicit adapter.

Thanks!

This bit me today as well with the following example:

conn = Faraday.new(url: "http://www.example.com") do |faraday|
  faraday.response :json, content_type: /\bjson$/
end

response = conn.get("stuff")

The error popped up in faraday_middleware when it tried to get the response headers when they were nil. A little misleading. Has a decision been made regarding the "best way" to handle this?

  • Use the default adapter if it's not specified (the best IMO)
  • Raise an error if one isn't specified
  • Update the README

I'd be more than happy to submit a PR

No need for a PR. We'll try to tackle this in the next release.

Thank you, @mislav.

Yeah, I wasted an hour yesterday chasing this one down. Nice to see a fix is on the way. When is the next release likely to land? Last one was January, which seems an awfully long time.

On Thu, Oct 2, 2014 at 5:49 PM, John Carney [email protected]
wrote:

When is the next release likely to land? Last one was January, which seems
an awfully long time.

Since 0.8 and 0.9 should be feature-frozen, the fix for this is likely to
only come in 1.0, which I'm not sure when it will be.

What about this for a simple middle ground: if an attempt is made to make a request when there is no adapter, an exception is raised.

Or is there a use case for "making requests" with no adapter?

On Fri, Oct 3, 2014 at 2:07 PM, John Bachir [email protected]
wrote:

What about this for a simple middle ground: if an attempt is made to make
a request when there is no adapter, an exception is raised.

How do we detect if an adapter is mounted?

Note that a custom adapter implemented by someone doesn't necessarily have
to subclass Faraday::Adapter.

Ah, I see. Because the adapter is a sibling of the other middleware, and the only api to test for is the standard rack api, there is no way to identify if a middleware is an http adapter. :sadtrombone:

If this is implemented, the changes in #496 can probably be reverted (if they were merged in the first place).

+1 to this. At the very least, an error that no adapter was specified, or better just use the default_adapter if nothing was specified.

The name is confusing, when you have to add middlewares you expect to just add middlewares and keep the default_adapter in place.

This is silly that we end up with no adapter at all. You never want that...

I just want to ensure everyone that we're internally discussing a strategy to manage this.
I know it sounds like an easy check, but as @mislav already explained we have an issue at the moment related with the freedom people have to add Adapters into the middlewares stack.
In fact, at the moment and "adapter" is nothing else than a middleware, like any other, performing the request.
Now, when you define your own middlewares stack, we loose the ability to detect the adapter from the other middlewares, unless you use the #adapter method when building the connection. Unfortunately, the use of it is not mandatory at the moment, making the adapter detection impossible.

Cool! Thanks for the headsup here:)

On Nov 14 2016, at 2:45 pm, Mattia [email protected] wrote:

I just want to ensure everyone that we're internally discussing a strategy
to manage this.
I know it sounds like an easy check, but as
@mislav already explained we have an issue at the
moment related with the freedom people have to add Adapters into the
middlewares stack.
In fact, at the moment and "adapter" is nothing else than a middleware, like
any other, performing the request.
Now, when you define your own middlewares stack, we loose the ability to
detect the adapter from the other middlewares, unless you use the #adapter
method when building the connection. Unfortunately, the use of it is not
mandatory at the moment, making the adapter detection impossible.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the
thread
.

https://v2.developer.pagerduty.com/docs/authentication

So the PagerDuty API docs, for ruby+faraday, exhibit this behavior; if you just use their code, it doesn't work. (Unless they fix it, which they probably will now that I pointed out the lack of an adapter.)

Honestly, my suggestion would be:

If I call "conn.get", and conn doesn't have an adapter, raise an exception telling me that. The current behavior is to return immediately but not have set things to the states Faraday describes as resulting from calling get. So for instance, ".status" is still nil. This is really surprising behavior! I would say that "I can't do anything because I have no idea what you want me to do" should probably produce some kind of diagnostic.

Hi @seebs I know it sounds silly, but I explained why this is not as easy to do as it could appear in my previous comment:

When you define your own middlewares stack, we loose the ability to detect the adapter from the other middlewares, unless you use the #adapter method when building the connection. Unfortunately, the use of it is not mandatory at the moment, making the adapter detection impossible.

I've been trying to trace through what happens, and I am missing something.

If you do the obvious thing that so many people do, and don't specify the adapter, when you call get, you eventually end up in rack_builder's build_response, which does app.call. And I'm... not actually sure where that goes, because I don't see where app got set.

So I guess what I'm wondering is, if you never do "adapter :foo", what ends up getting the call?

One trivial solution:
def initialize(handlers = [])
@handlers = handlers
if block_given?

  • self.adapter Faraday.default_adapter
    build(&Proc.new)
    elsif @handlers.empty?

... I think this would work (-ish)?

Alternatively, after build(&Proc.new), check whether self.adapter has been set.

Even though adapter :foo is the most used way to setup the adapter, there are other ways to do that.
#adapter is in fact just an helper, but you can use #use to put an adapter in the stack:

conn = Faraday.new(url: "http://www.example.com") do |faraday|
  faraday.response :logger
  faraday.use Faraday::Adapter::NetHttp
end

So what is an adapter at the moment? Nothing more than a middleware that perform the call and store the result in env. That's why we can't simply rely on the #adapter method being called. We can't event rely on the middleware that works as an adapter to inherit from Faraday::Adapter because that's not a requirement either.

The only solution is to use Faraday 1.0 release to make mandatory the use of the #adapter method, or to assume that it will be used and push the default adapter in the stack if the method is not called.

We're still discussing internally on which one would be the best solution as one way or another we'll end up having people getting unexpected results after the update.
Which is what's happening to a lot of people the first time they use Faraday, I agree, but is definitely worse when it happens on your production system after you run bundle update :)

I think what I'm not understanding is... If you never set up an adapter, what ends up handling the call and storing the result? There's an invocation that, if you've picked an adapter, uses the adapter's call() method, I think. If you haven't picked an adapter, what code actually gets executed?

This is where I picked the adapter, without using the #adapter method.

  faraday.use Faraday::Adapter::NetHttp

Take my snippet from my previous comment and try it, it will just work.
The thing is that you can push an adapter in the stack in many ways, and once it's there it will be used in the request (calling the #call method)

Sorry, I was apparently unclear. I'm aware that you can pick an adapter in multiple ways.

But consider the common newbie mistake of not picking an adapter, and then calling conn.get(...). What actually ends up happening, apart from "nothing that has any effect"?

I tried to trace through the calls, and was not able to figure out what actually ends up happening. It looks like Connection will have defined a default get which calls run_request. That in turn calls build_request and build_response, and I think that ends up in rack_builder's build_response(), which is calling app.call(), and it appears that app is generating a Response object which handles the call(). But then I get confused. I don't see Response even defining a call() method, and I can't figure out what ends up actually getting the call() method invoked.

I am sort of assuming that there is something which has a call method, which gets invoked. If you have selected an adapter, you get that adapter's call method. If you haven't selected an adapter, where does the call method that actually executes come from?

After setting up the request and the response, Connection go through every middleware and calls the call(env) method on them, in the same order as the stack is populated.
It does not make any distinction between "normal middlewares" and the adapter, it just assumes that one of the middlewares IS the adapter. If there's no "adapter middleware" in the stack, all other middlewares will be called (call method), but none of them will effectively perform the request (that's the adapter role).
Which means the response will be missing the status, the body, and all other fields that get populated by the "adapter middleware".

I understand it's not very easy, but I hope it's clearer now 😅

Ah-hah! That's what I'd failed to understand. It didn't occur to me that they'd all provide call() functions.

What if, after calling everything, the code raised an exception if status isn't set? "StatusNotSetException: Status was not set. Most likely, your middleware does not specify an adapter to actually do the network fetch."

I’d really love to see this fixed!

Fixed in #750 @iMacTia - would you close this?

@olleolleolle yeah good point! I thought this was going to be closed automatically by mentioning it :(

It looks like this was never released? Is there any chance of seeing a minor release before 1.0? :speak_no_evil:

@franzliedke you're right, this is currently included in v1.0 and I agree it was merged quite some time ago 😅.
We did a lot of progresses towards v1.0 though and I'd say we're fairly close to get it over the line.
Try giving us a little more time, it will be worth the wait 😃

Great to hear!

Sorry for coming across a bit demanding - I know how stressful open-source can be. Thanks for the great software!

Not at all @franzliedke !
Appreciate the nudge, we should really aim and release v1.0 sooner rather than later.
Watch the repo if you don't already so you won't miss the launch 👍

Was this page helpful?
0 / 5 - 0 ratings

Related issues

QuinnWilton picture QuinnWilton  ·  4Comments

JasonBarnabe picture JasonBarnabe  ·  4Comments

mvastola picture mvastola  ·  4Comments

mattmill30 picture mattmill30  ·  4Comments

ioquatix picture ioquatix  ·  4Comments