How to upgrade a http conection to websocket on phoenix?

I have a endpoint that fetchs value of Mnesia, and if succeeds, I want to stablish a tcp connect between the client and the server, how do I do that?
find.controller.ex

defmodule TestespayWeb.IdentifyController do
  alias TestespayWeb.Identify.Service, as: ControllerService
  require Logger
  use TestespayWeb, :controller

  def find(conn, %{"payload" => payload}) do
    with {:ok, light_ram_contract, status, datalayer} <- ControllerService.get_ram_contract_all_sources(payload) do
      Logger.debug("Contract found, sending response")
      conn
      |> put_resp_content_type("application/json")
      |> put_resp_header("X-Transaction-Status", Atom.to_string(status)) # Adiciona o status no cabeçalho
      |> put_resp_header("X-Datalayer", Atom.to_string(datalayer))
      |> send_resp(200, map_tojson(%{contract: light_ram_contract}))
    else
      {:error, reason, _status, datalayer} ->
        Logger.debug("Contract not found, sending error response")
        conn
        |> put_resp_content_type("application/json")
        |> put_resp_header("X-Transaction-Status", "not_found")
        |> put_resp_header("X-Datalayer", Atom.to_string(datalayer))
        |> send_resp(404, map_tojson(%{error: reason}))
    end
  end

  def map_tojson(map) do
    {:ok, json_resp} = Jason.encode(map)
    json_resp
  end

  def json_tomap(json) do
    {:ok, map_resp} = Jason.decode(json)
    map_resp
  end
end

endpoint

defmodule TestespayWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :testespay

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  # Set :encryption_salt if you would also like to encrypt it.
  @session_options [
    store: :cookie,
    key: "_testespay_key",
    signing_salt: "sLyVi0bu",
    same_site: "Lax"
  ]

  socket "/ws/wallet", TestespayWeb.WalletSocket,
  websocket: true,
  longpoll: false


  socket "/live", Phoenix.LiveView.Socket,
    websocket: [connect_info: [session: @session_options]],
    longpoll: [connect_info: [session: @session_options]]

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phx.digest
  # when deploying your static files in production.
  plug Plug.Static,
    at: "/",
    from: :testespay,
    gzip: false,
    only: TestespayWeb.static_paths()

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
    plug Phoenix.LiveReloader
    plug Phoenix.CodeReloader
    plug Phoenix.Ecto.CheckRepoStatus, otp_app: :testespay
  end

  plug Phoenix.LiveDashboard.RequestLogger,
    param_key: "request_logger",
    cookie_key: "request_logger"

  plug Plug.RequestId
  plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Phoenix.json_library()

  plug Plug.MethodOverride
  plug Plug.Head
  plug Plug.Session, @session_options
  plug TestespayWeb.Router
end

my idea is to fetch the record on the http connect, them establish a websocket to fetch the state of the record

:wave:

Since many WebSocket clients (including the browser API) don’t provide any extra info on upgrade and connection errors, it might be easier to separate the connection into two actions:

  • validate the request and return a signed token in case of success, handle the error in your preferred way – you have the full HTTP response
  • use the signed token to connect to the WebSocket endpoint

Might be useful: Plug — Plug v1.16.1

2 Likes

so I will have to use “vanilla” plug? I thought phoenix had a built in solution

A server cannot unilaterally instruct a client to upgrade an existing HTTP request to a WebSocket connection. The WebSocket protocol upgrade must be initiated by the client as part of the HTTP request headers.

Did you consider using LiveView?

With LiveView you will have a persistent connection between client and server ready for bidirectional communication.

If you really need something custom, I’d look at the docs of either Cowboy or Bandit, depending on which one you’re using. There’s a WebSock specification in Phoenix which is common to both implementations.

https://hexdocs.pm/websock/readme.html
https://ninenines.eu/docs/en/cowboy/2.12/guide/ws_protocol/

You’d also need to write JS code to instruct the browser to make a WebSocket connection.

1 Like

the thing is the client will be built by me, so I just want to know how I can only accept the connection from the client, doing the request from the client (js console)

just pass whatever you’re using to identify the client as params when connecting to the socket, and validate accordingly on the connect callback of your socket in the backend.
it’s one of the opts when creating a new socket: