Same request fails with some adapters and succeeds with others
Hello,
I am migrating some of our requests from HTTPoison to Tesla and I found some requests are failing if I use httpc or Mint as the adapters, while Hackney works. I understand that it is because of HTTPoison using Hackney under the hood.
It would be nice, however to see some sort of uniformity in handling requests so that switching to a new adapter can be as seamless as possible.
uri = <URI>
method = :post
body = nil
headers: [
{"Content-type", "application/json"},
{"Accept", "application/json"},
{"Authorization",
"Bearer <TOKEN>"}
]
query = []
Tesla.request(client(),
method: method,
url: uri,
body: body,
headers: headers,
query: query
)
defp client() do
Tesla.client([Tesla.Middleware.JSON])
end
If I run that with Hackney it works well, if I use httpc for example I get this error
** (FunctionClauseError) no function clause matching in :httpc.request/5
The following arguments were given to :httpc.request/5:
# 1
:post
# 2
{'<URL>',
[
{'Content-type', 'application/json'},
{'Accept', 'application/json'},
{'Authorization',
'Bearer <TOKEN>'}
]}
# 3
[autoredirect: false]
# 4
[]
# 5
:default
(inets) httpc.erl:149: :httpc.request/5
(tesla) lib/tesla/adapter/httpc.ex:52: Tesla.Adapter.Httpc.request/2
(tesla) lib/tesla/adapter/httpc.ex:22: Tesla.Adapter.Httpc.call/2
(tesla) lib/tesla/middleware/json.ex:54: Tesla.Middleware.JSON.call/3
Seems like an issue with adapters not handling POST request with nil body.
@gaiabeatrice Could you provide a test case in https://github.com/teamon/tesla/blob/master/test/support/adapter_case/basic.ex ?
- Httpc does not accept nil as body. See http://www1.erlang.org/doc/man/httpc.html
- Tesla uses
anyfor body: https://hexdocs.pm/tesla/Tesla.Env.html#t:body/0 - Hackney accepts
term(any) for body: https://hexdocs.pm/hackney/hackney.html#request-5
You can just pass empty string ("").
{:ok, _} =
Tesla.client([Tesla.Middleware.JSON], Tesla.Adapter.Httpc)
|> Tesla.post("https://httpbin.org/post", "")
We may handle the nil value at Tesla.Adapter.Httpc as it's easy to handle - but at the same time I'm not sure how much we have to make adapter behave exactly same way, since at some level it's not possible at all.
I think it makes sense to:
- [ ] Add a basic adapter test case to sending POST request with
nilbody - [ ] Handle
nilbody for adapters that fail
Hey @teamon I'm now to tesla but would like to help on this issue. I'll try to first create the test that fails. Any other guidance?
I've just seen that @gaiabeatrice has open a PR for this so I'll find some other issue to work on
I couldn't test :gun, I kept getting the following error:
iex(2)> {:ok, _} = Application.ensure_all_started(:gun)
** (MatchError) no match of right hand side value: {:error, {:cowlib, {'no such file or directory', 'cowlib.app'}}}
But, all the other adapters support passing nil body as far as I can tell:
{:ok, _} = Application.ensure_all_started(:hackney)
# {:ok, _} = Application.ensure_all_started(:gun)
{:ok, _} = :ibrowse.start()
{:ok, _} = Finch.start_link(name: MyFinch)
adapters = [
{Tesla.Adapter.Httpc, []},
{Tesla.Adapter.Hackney, []},
{Tesla.Adapter.Mint, []},
{Tesla.Adapter.Ibrowse, []},
{Tesla.Adapter.Finch, [name: MyFinch]},
# {Tesla.Adapter.Gun, []},
]
[nil, nil, nil, nil, nil] = for adapter <- adapters do
client = Tesla.client([Tesla.Middleware.JSON], adapter)
{:ok, res} = Tesla.post(client, "https://httpbin.org/post", nil)
nil = res.body["json"]
end
uri = "https://httpbin.org/post"
method = :post
body = nil
headers = [
{"Content-type", "application/json"},
{"Accept", "application/json"},
{"Authorization",
"Bearer <TOKEN>"}
]
query = []
{:ok, _} = Tesla.request(Tesla.client([Tesla.Middleware.JSON]),
method: method,
url: uri,
body: body,
headers: headers,
query: query
)
I will close this. If you face an issue with :gun package, please open a new issue, but as far as I can tell. It is working.