簡體   English   中英

為什么在重定向到非實時視圖頁面后會再次安裝此實時視圖?

[英]Why is this liveview mounting again after being redirected to a non-liveview page?

我在一個項目中工作,該項目主要是從phx.newphx.gen.auth新生成的網絡應用程序。 我有一個非實時查看的索引頁面。 登錄后,用戶被重定向到主頁,這是一個實時視圖。

期望:點擊生成的Log out鏈接后,用戶應該被重定向到/索引頁面,這不是一個實時視圖。 此行為由生成的身份驗證指定。

經驗:問題是當我單擊生成的Log out鏈接時,而不是被重定向到注銷的索引啟動頁面,因為生成的身份驗證是為了執行而我被重定向到登錄頁面,在那里我看到兩條 flash 消息:一條:info flash 表示成功注銷,第二條:error flash 抱怨“你必須登錄才能訪問此頁面。”我不希望用戶在登錄頁面上看到:error flash,以及更糟糕的是,我認為,出現:error flash 的原因是因為PageLive沒有出現在索引頁面上,它再次運行它的mount/3功能(第三次),這導致liveview身份驗證再次運行,並導致第二次重定向。重要的是,這個問題間歇性地發生,即有時重定向工作正常並將用戶發送到索引頁面沒有問題,而其他時候第二次冗余重定向和錯誤的閃現消息是顯示。我認為這個表示某種競爭條件。

我有一個相對較新生成的項目,其中包含這些路線(以及其他路線):

router.ex

  scope "/", MyappWeb do
    pipe_through :browser

    live_session :default do
      live "/dash", PageLive, :index
    end
  end

  scope "/", MyappWeb do
    pipe_through [:browser, :redirect_if_user_is_authenticated]

    get "/", PageController, :index
  end

  scope "/", MyappWeb do
    pipe_through [:browser]

    delete "/users/log_out", UserSessionController, :delete
  end

身份驗證由phx.gen.auth生成。 生成的UserSessionControllerdelete操作會觸發生成的UserAuth.log_out_user/1觸發。

user_session_controller.ex

  def delete(conn, _params) do
    conn
    |> put_flash(:info, "Logged out successfully.")
    |> UserAuth.log_out_user()
  end

user_auth.ex

  def log_out_user(conn) do
    user_token = get_session(conn, :user_token)
    user_token && Accounts.delete_session_token(user_token)

    if live_socket_id = get_session(conn, :live_socket_id) do
      MyappWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
    end

    conn
    |> renew_session()
    |> delete_resp_cookie(@remember_me_cookie)
    |> redirect(to: "/")
  end

路由器中的實時路由到/dash路由通過一個名為PageLive的實時PageLive ,它只是安裝在一些身份驗證上,如PageLive 文檔中所推薦的:

page_live.ex

defmodule MyappWeb.PageLive do
  use MyappWeb, :live_view
  alias MyappWeb.Live.Components.PackageSearch
  alias MyappWeb.Live.Components.Tabs
  alias MyappWeb.Live.Components.Tabs.TabItem
  on_mount MyappWeb.UserLiveAuth
end

user_live_auth.ex

defmodule MyappWeb.UserLiveAuth do
  import Phoenix.LiveView, only: [assign_new: 3, redirect: 2]
  alias Myapp.Accounts
  alias Myapp.Accounts.User
  alias MyappWeb.Router.Helpers, as: Routes

  def mount(_params, session, socket) do
    socket =
      assign_new(socket, :current_user, fn ->
        find_current_user(session)
      end)

    case socket.assigns.current_user do
      %User{} ->
        {:cont, socket}

      _ ->
        socket =
          socket
          |> put_flash(:error, "You must be logged in to access this page.")
          |> redirect(to: Routes.user_session_path(socket, :new))

        {:halt, socket}
    end
  end

  defp find_current_user(session) do
    with user_token when not is_nil(user_token) <- session["user_token"],
         %User{} = user <- Accounts.get_user_by_session_token(user_token),
         do: user
  end
end

以下是用戶點擊注銷后的進程日志:

**[info] POST /users/log_out**
[debug] Processing with MyappWeb.UserSessionController.delete/2
  Parameters: %{"_csrf_token" => "ET8xMSU5KSEedycKEAcJfX0JCl45LmcF_VEHANhinNqHcaz6MFRkIqWu", "_method" => "delete"}
  Pipelines: [:browser]
[debug] QUERY OK source="users_tokens" db=1.8ms idle=389.7ms
SELECT u1."id", u1."email", u1."hashed_password", u1."confirmed_at", u1."first_name", u1."last_name", u1."username", u1."inserted_at", u1."updated_at" FROM "users_tokens" AS u0 INNER JOIN "users" AS u1 ON u1."id" = u0."user_id" WHERE ((u0."token" = $1) AND (u0."context" = $2)) AND (u0."inserted_at" > $3::timestamp + (-(60)::numeric * interval '1 day')) [<<159, 144, 113, 83, 223, 12, 183, 119, 50, 248, 83, 234, 128, 237, 129, 112, 138, 147, 148, 100, 67, 163, 50, 244, 127, 26, 254, 184, 102, 74, 11, 52>>, "session", ~U[2021-10-06 22:13:44.080128Z]]
[debug] QUERY OK source="users_tokens" db=1.7ms idle=391.8ms
DELETE FROM "users_tokens" AS u0 WHERE ((u0."token" = $1) AND (u0."context" = $2)) [<<159, 144, 113, 83, 223, 12, 183, 119, 50, 248, 83, 234, 128, 237, 129, 112, 138, 147, 148, 100, 67, 163, 50, 244, 127, 26, 254, 184, 102, 74, 11, 52>>, "session"]
**[info] Sent 302 in 6ms**
**[info] CONNECTED TO Phoenix.LiveView.Socket in 64µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" =>** "ET8xMSU5KSEedycKEAcJfX0JCl45LmcF_VEHANhinNqHcaz6MFRkIqWu", "_mounts" => "0", "_track_static" => %{"0" => "http://localhost:4000/assets/app.css", "1" => "http://localhost:4000/assets/app.js"}, "vsn" => "2.0.0"}
[debug] QUERY OK source="users_tokens" db=1.6ms idle=422.5ms
SELECT u1."id", u1."email", u1."hashed_password", u1."confirmed_at", u1."first_name", u1."last_name", u1."username", u1."inserted_at", u1."updated_at" FROM "users_tokens" AS u0 INNER JOIN "users" AS u1 ON u1."id" = u0."user_id" WHERE ((u0."token" = $1) AND (u0."context" = $2)) AND (u0."inserted_at" > $3::timestamp + (-(60)::numeric * interval '1 day')) [<<159, 144, 113, 83, 223, 12, 183, 119, 50, 248, 83, 234, 128, 237, 129, 112, 138, 147, 148, 100, 67, 163, 50, 244, 127, 26, 254, 184, 102, 74, 11, 52>>, "session", ~U[2021-10-06 22:13:44.110158Z]]
**[info] GET /users/log_in**
[debug] Processing with MyappWeb.UserSessionController.new/2
  Parameters: %{}
  Pipelines: [:browser, :redirect_if_user_is_authenticated]
[info] Sent 200 in 6ms

注意在上面的日志中,302 重定向是如何發生的,然后套接字立即重新連接並運行mount/3 ,然后觸發另一個重定向,這次是到/users/log_in路由。 據我所知,套接字不應該在這里嘗試重新連接,我看不出是什么觸發了這個。

為什么在注銷時302重定向到非實時查看頁面后再次觸發PageLive掛載,從而觸發第二次重定向到登錄頁面?

他們的關鍵是這個代碼

MyappWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})

log_out_user/1 在這里,您通過 Erlang 消息斷開實時取景套接字。

這會觸發套接字關閉服務器端(請參閱Phoenix.Socket

def __info__(%Broadcast{event: "disconnect"}, state) do
  {:stop, {:shutdown, :disconnected}, state}
end

但是它是一個實時視圖,它會在通過客戶端 javascript 斷開連接后重新連接,然后重新安裝實時視圖,這會導致MyappWeb.UserLiveAuth再次添加 Flash 消息。

作為參考, 請查看 LiveView 文檔

一旦 LiveView 斷開連接,客戶端將嘗試重新建立連接並重新執行 mount/3 回調。 在這種情況下,如果用戶不再登錄或不再訪問當前資源,mount/3 將失敗並且用戶將被重定向。

一個潛在的解決方案可能是,在路由管道內的插件而不是掛載中執行 flash + 重定向,因此在加載頁面時將重定向未登錄的用戶,然后在{:halt, socket}中實時取景安裝,因此注銷時沒有重定向。 或者,在注銷請求已經重定向后發送廣播以斷開連接(也許分離異步Task可能會有所幫助)。

所以也許可以像這樣包裝廣播,讓瀏覽器關閉實時視圖本身(同時仍然重定向所有其他打開的實時視圖):

Task.async(fn -> 
  :timer.sleep(1000) # Give the browser some time to process the response and close the LV
  MyappWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
end)

user_auth.ex ,我有這些行,它們從數據庫中刪除用戶會話:

  def log_out_user(conn) do
    user_token = get_session(conn, :user_token)
    user_token && Accounts.delete_session_token(user_token)

出現問題的原因是實時安裝未通過身份驗證,因為會話令牌不再存在於數據庫中。 這會在常規UserAuth.log)out_user/1重定向有機會觸發之前觸發重定向。

受@smallbutton 解決方案的啟發,我使用確保在log_out_user/1重定向之后不會刪除會話令牌以避免競爭條件:

  def log_out_user(conn) do
    if live_socket_id = get_session(conn, :live_socket_id) do
      MyAppWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
    end

    # TODO is there a better way to handle this issue?
    Task.async(fn ->
      :timer.sleep(1000)
      user_token = get_session(conn, :user_token)
      user_token && Accounts.delete_session_token(user_token)
    end)

    conn
    |> renew_session()
    |> delete_resp_cookie(@remember_me_cookie)
    |> redirect(to: "/")
  end

不過,這是一個不令人滿意的解決方法,因為我不喜歡像這樣手動協調事件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM