Sending HTTPoison from phenix app to a device on the same network

Hello,

I am trying to send a request to a server on the same network.

  def unload_model(conn, %{"id" => id}) do
    case Frontend.Repo.get(LoadedModel, id) do
      nil ->
        {:error, "LoadedModel with id #{id} not found"}

      loaded_model ->
        # Construct the URL and payload for the DELETE request
        url = "http://10.0.20.78:8000/admin/unoad_model"
        payload = %{model_id: id}

        headers = [
          {"Content-Type", "application/json"},
          {'api_key', conn.assigns.current_user.api_key}
        ]

        # Send the DELETE request to the server
        case send_delete_request(url, payload, headers) do
          {:ok, _response} ->
            {:ok, "Model unloaded successfully"}

          {:error, reason} ->
            {:error, "Failed to unload model: #{reason}"}
        end
    end
  end
  defp send_delete_request(url, payload, headers) do
    case HTTPoison.delete(url, body: Jason.encode!(payload), headers: headers) do
      {:ok, response} ->
        {:ok, response}
      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, "HTTPoison error: #{inspect(reason)}"}
    end
  end

Earlier i got errors regarding SSL certificate:

{:tls_alert, {:unknown_ca, 'TLS client: In state wait_cert_cr at ssl_handshake.erl:2111 generated CLIENT ALERT: Fatal - Unknown CA\n'}}

After that, i started getting a new error:

errors were found at the given arguments:
  * 1st argument: not an iodata term

I think this is whats causing the error
:erlang.list_to_binary([{"Content-Type", "application/json"}, {"api_key", "...."}])

i assume the error is related to the body of the request being unrealizable or something like that, i’ve tried many things, but i don’t know what else to do.

Thanks

From: IO — Elixir v1.16.1

IO data is a data type that can be used as a more efficient alternative to binaries in certain situations.

A term of type IO data is a binary or a list containing bytes (integers within the 0..255 range) or nested IO data. The type is recursive. Let’s see an example of one of the possible IO data representing the binary "hello":

[?h, "el", ["l", [?o]]]

The built-in iodata/0 type is defined in terms of iolist/0. An IO list is the same as IO data but it doesn’t allow for a binary at the top level (but binaries are still allowed in the list itself).

In your case, the list you provide to :erlang.list_to_binary/1 contains tuples, which is not allowed in an iolist.

1 Like

Thank you for replying,
So what other datatype do i use to send the headers, i am following the docs and the y send it the same way i am, also i never use :erlang.list_to_binary, but i assume its used when creating a list? or when passing a list to HTTPoison?

I am not familiar with HTTPoison, but the docs for HTTPoison.get/3 seem to suggest that headers can be given as the second argument:

get(url, headers \\ [], options \\ [])

See the type definition for headers.

headers() ::
  [{atom(), binary()}]
  | [{binary(), binary()}]
  | %{required(binary()) => binary()}
  | any()

So it looks like you do not need to convert your headers (it’s already a list of {binary, binary}-tuples. Not sure where the error is coming from though. Perhaps some part or element of your headers-list is not conform above spec?

1 Like

Can you post the stack trace of the error? The call to list_to_binary suggests that the code has gone down the wrong path someplace, but it’s hard to tell without the trace.

One thing I’m suspicious about - does the behavior change if you correct this to "api_key" (a binary) instead of 'api_key' (a charlist)?

Thank you al2o3cr for replying,
you make a great point of changing the the values in the list from chars to binaries, but unfortunately, it didn’t work and i got the same error, i’ve tried multiple combinations of it,{'api_key', conn.assigns.current_user.api_key} - {'api_key', '#{conn.assigns.current_user.api_key}'} - {"api_key", "#{conn.assigns.current_user.api_key}"} but none have worked, same error on the headers.

btw removing the headers sends the request successfully, so it is the headers

here’s the stack trace

Request: DELETE /users_management/266/unload_model
** (exit) an exception was raised:
    ** (ArgumentError) errors were found at the given arguments:

  * 1st argument: not an iolist term

        :erlang.list_to_binary([{"Content-Type", "application/json"}, {"api_key", "..."}])
        (hackney 1.20.1) c:/.../frontend/deps/hackney/src/hackney_bstr.erl:36: :hackney_bstr.to_binary/1
        (hackney 1.20.1) c:/.../frontend/deps/hackney/src/hackney_headers_new.erl:193: anonymous fn/3 in :hackney_headers_new.to_iolist/1
        (hackney 1.20.1) c:/.../frontend/deps/hackney/src/hackney_headers_new.erl:148: :hackney_headers_new.do_fold/3
        (hackney 1.20.1) c:/.../frontend/deps/hackney/src/hackney_headers_new.erl:184: :hackney_headers_new.to_iolist/1
        (hackney 1.20.1) c:/.../frontend/deps/hackney/src/hackney_request.erl:101: :hackney_request.perform/2
        (hackney 1.20.1) c:/.../frontend/deps/hackney/src/hackney.erl:378: :hackney.send_request/2
        (httpoison 2.2.1) lib/httpoison/base.ex:888: HTTPoison.Base.request/6
        (frontend 0.1.0) lib/frontend_web/controllers/user_management_conroller.ex:317: FrontendWeb.UserManagementController.send_delete_request/3
        (frontend 0.1.0) lib/frontend_web/controllers/user_management_conroller.ex:303: FrontendWeb.UserManagementController.unload_model/2
        (frontend 0.1.0) lib/frontend_web/controllers/user_management_conroller.ex:1: FrontendWeb.UserManagementController.action/2
        (frontend 0.1.0) lib/frontend_web/controllers/user_management_conroller.ex:1: FrontendWeb.UserManagementController.phoenix_controller_pipeline/2
        (phoenix 1.7.2) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
        (frontend 0.1.0) lib/frontend_web/endpoint.ex:1: FrontendWeb.Endpoint.plug_builder_call/2
        (frontend 0.1.0) deps/plug/lib/plug/debugger.ex:136: FrontendWeb.Endpoint."call (overridable 3)"/2
        (frontend 0.1.0) lib/frontend_web/endpoint.ex:1: FrontendWeb.Endpoint.call/2
        (phoenix 1.7.2) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
        (plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
        (cowboy 2.9.0) c:/.../frontend/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.9.0) c:/.../frontend/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3

sorry for the delayed response

I might be missing something but according to the HTTPoison docs the function signature is delete(url, headers \\ [], options \\ []) and not delete(url, body, headers) as you have in your code?

https://hexdocs.pm/httpoison/HTTPoison.html#delete/3

DELETE requests don’t typically have a request body I think, you might want to use a POST if you need to include a body.

Or use request directly with your body and the delete method?
https://hexdocs.pm/httpoison/HTTPoison.html#request/5

Yes _jonas you are totally right, i realized that a while ago, and changed it to a POST, but i got the same error, the problem is stemming from before the request getting sent, its an elixir or erlang error, or it could be a HTTPoison error, i really cannot tell.

I see, maybe you could share the current complete code?

good suggestion

 def unload_model(conn, %{"id" => id}) do
    case Frontend.Repo.get(LoadedModel, id) do
      nil ->
        {:error, "LoadedModel with id #{id} not found"}

      loaded_model ->
        # Construct the URL and payload for the DELETE request
        url = "http://10.0.20.78:8000/admin_unload_model"
        payload = %{model_id: id, api_key: conn.assigns.current_user.api_key}

        headers = [
          {"Content-Type", "application/json"},
          {"api_key", "#{conn.assigns.current_user.api_key}"}
        ]

        # Send the POST request to the server
        case send_request(url, payload, headers) do
          {:ok, _response} ->
            conn
            |> put_status(:ok)
            |> json(%{message: "Model unloaded"})

          {:error, reason} ->
            {:error, "Failed to unload model: #{reason}"}
        end
    end
  end

  defp send_request(url, payload, headers) do
    # Use an HTTP client library to send the POST request
    case HTTPoison.post!(url, body: Jason.encode(payload), headers: headers) do
      {:ok, response} ->
        {:ok, response}

      {:error, error} ->
        {:error, error}
    end
  end

btw
Erlang/OTP 25 [erts-13.2] [source] [64-bit] [smp:32:32] [ds:32:32:10] [async-threads:1] [jit:ns] Mix 1.14.4 (compiled with Erlang/OTP 25)
and {:httpoison, “~> 2.2”}

I think this

Should be just

case HTTPoison.post!(url, Jason.encode(payload), headers) do

Take this example from the docs for reference:

https://hexdocs.pm/httpoison/HTTPoison.html#request/5-examples

I’m also just learning Elixir but I think body: Jason.encode(payload), headers: headers) is syntactic sugar for [body: Jason.encode(payload), headers: headers)] meaning both arguments become a single keyword list that is used as the second argument of the function call.
Someone correct me if I’m wrong here.

1 Like

You are absolutely right thank you so much, if i have follow up errors do i continue on this thread, i don’t know the etiquette here.

1 Like