MTheProgrammer
Standard HTTP Client behaviour - feature request
Hello,
I am working on a SSO implementation for Line Login and Ueberauth. New version was released but the current maintainer did not update API for Line Login v2.1.
What I’ve found is that each Ueberauth SSO implementation can use it’s very own HTTP client.
I feel is that developer should be able to inject desired HTTP client for all Elixir modules.
Unfortunately, there is no such configuration yet.
This forum has a topic about standard HTTP library implemented in the Elixir and disadvantages of multiple clients for each library:
However, as michalmuskala pointed it would require maintenance by the Elixir team.
I’m coming from the OOP world and dependency inversion is common here. For example, PHP has the PSR - PHP standard recommendations - interfaces defining a contract between libraries and applications.
Here is the example: PSR-18: HTTP Client - PHP-FIG
Another example for Logger: PSR-3: Logger Interface - PHP-FIG
Is it possible to have one standarized client API, e.g. a behaviour, that could be implemented by various HTTP clients? I am curious if this methodology is in line with the Elixir/functional philosophy.
Most Liked Responses
wojtekmach
After implementing a bunch of HTTP client behaviour modules for different projects and libraries I realised that vast majority of time we don’t need a module, all we need is a function. (Something to keep in mind for other things too.)
Here’s a signature:
f(options) :: {:ok, %{status:, status, headers: headers, body: body}} | {:error, exception}
where options contains :method, :url, :headers, :body and other arbitrary adapter-specific keys.
And then we’d use it like this: Foo.bar(http_request: &MyApp.http_request/1). And if we want to pass options at callsite we’d do: http_request: {&MyApp.http_request/1, receive_timeout: 1000}.
(It is pure accident that with next version of Req, an http client that I’m working on, it’d be: http_request: &Req.request/1. ;))
al2o3cr
Some thoughts in no particular order:
-
in the request,
headersshould not be amap- order is important, and the same header can appear multiple times. Passing amapin theheadersposition to:hackney.requestwill get you aMatchError… -
in the response type,
bodyis amapbut the Hackney implementation puts the output of:hackney.bodythere, which is specced asbinary -
loading the entire reply body into memory is probably OK, but there’s a reason that Hackney doesn’t do it automatically. Advanced use cases would want to use
:hackney.stream_bodyor similar and there’s no way to do that with this API -
the response for connection errors is
{:error, binary()}which is universal but also opaque. It’s also not satisified by the Hackney adapter, which will sometimes return errors like{:error, :connect_timeout}. Clients may want to handle situations like “no network connectivity” specifically, but that’s not possible with this interface short of pattern-matching on the error message. -
there’s no way to pass options from the caller down to the adapter, for things like proxy configuration or Hackney’s
pooloption. Libraries that depend onLmHttpClientshould be indifferent to the adapter in use, but the end-user configuring their system is not. -
the interface forces a synchronous request, which isn’t always desirable in a BEAM program. There are alternatives nowadays like
:gunor Mint that divide the work up differently.
MTheProgrammer
Thank you for the fast response and your insights.
the interface forces a synchronous request
Isn’t that responsibility of the business domain to spawn new Tasks? Sometimes order of requests is important.
Neither dialyxyr nor credo found these mismatching return types you mentioned. How can we ensure the data flow integrity?
Back to the topic, do you feel that standardizing the HTTP Api for Elixir is reasonable?








