mtrudel/bandit

Bandit intercepting liveview send/2 messages

ringofhealth opened this issue · 4 comments

Hi again!

in our app, we are using liveview, and we are using send/2 and handl_info in :liveview

after switch to bandit, we been noticing that bandit is intercepting the handle_info call within the liveview

[error] GenServer #PID<0.5234.0> terminating
** (FunctionClauseError) no function clause matching in Bandit.HTTP1.Handler.handle_info/2
    (bandit 1.0.0-pre.3) lib/thousand_island/handler.ex:5: Bandit.HTTP1.Handler.handle_info({:update_user_view_count, "21691d84-2656-4556-9b02-f87c3f94bbbd"}, {%ThousandIsland.Socket{socket: #Port<0.216>, transport_module: ThousandIsland.Transports.TCP, read_timeout: 60000, span: %ThousandIsland.Telemetry{span_name: :connection, telemetry_span_context: #Reference<0.2582905735.3298820105.108121>, start_time: -576455428605852008, start_metadata: %{parent_telemetry_span_context: #Reference<0.2582905735.3298820098.52671>, remote_address: {127, 0, 0, 1}, remote_port: 56410, telemetry_span_context: #Reference<0.2582905735.3298820105.108121>}}}, %{handler_module: Bandit.HTTP1.Handler, http_1_enabled: true, http_2_enabled: true, opts: %{http_1: [], http_2: [], websocket: []}, plug: {Phoenix.Endpoint.SyncCodeReloadPlug, {HelioWeb.Endpoint, []}}, requests_processed: 10, websocket_enabled: true}})

here is how the code is being called in our liveview

  def mount(
        %{"id" => id},
        _session,
        %{assigns: %{current_user: current_user}} = socket
      ) do
    user = Accounts.get_by_username!(id)
    send(self(), {:update_user_view_count, user.id})

    {:ok,
     socket}
  end

  @impl true
  def handle_info({:update_user_view_count, user_id}, socket) do
    # Posts.update_post_field_count({:view, post_id})
    Helio.Posts.BatchCounter.Supervisor.increment_user({:view, user_id})

    {:noreply, socket}
  end

any guidance is appreciated

Is this a websocket or long poll based liveview?

Try it like this:

  def mount(
        %{"id" => id},
        _session,
        %{assigns: %{current_user: current_user}} = socket
      ) do
    user = Accounts.get_by_username!(id)

    if connected?(socket), do: send(self(), {:update_user_view_count, user.id})

    {:ok,
     socket}
  end

If the LiveView isn't connected yet, your self() will return the Bandit.DelegatingHandler and not your LiveView.
(The message will still be send to your LiveView so your implementation will work, but the send will be called twice)

Is this a websocket or long poll based liveview?

This is websocket

Try it like this:

  def mount(
        %{"id" => id},
        _session,
        %{assigns: %{current_user: current_user}} = socket
      ) do
    user = Accounts.get_by_username!(id)

    if connected?(socket), do: send(self(), {:update_user_view_count, user.id})

    {:ok,
     socket}
  end

If the LiveView isn't connected yet, your self() will return the Bandit.DelegatingHandler and not your LiveView. (The message will still be send to your LiveView so your implementation will work, but the send will be called twice)

will try this and report back! thank!

See https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#connected?/1 for docs on this; I think this ought to be more prominent in the live view docs (or maybe even a explicit flag that gets passed to mount) as it's an easy point of confusion.

Closing since I'm 99% sure that @moogle19's advice will work. If not, feel free to reopen!