首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么这个liveview在被重定向到一个非liveview页面后会再次挂载?

为什么这个liveview在被重定向到一个非liveview页面后会再次挂载?
EN

Stack Overflow用户
提问于 2021-10-01 17:42:10
回答 2查看 647关注 0票数 1

我正在做一个项目,这个项目主要是来自phx.newphx.gen.auth的一个新生成的网络应用程序。我有一个非实时视图的索引页面。登录后,用户将被重定向到主页面,这是一个liveview。

期望:单击生成的Log out链接后,应该将用户重定向到/索引页面,这不是一个动态视图。此行为由生成的身份验证指定。

经验:问题是,当我单击生成的Log out链接时,没有像编写生成的身份验证那样被重定向到已注销的索引启动页,而是重定向到登录页面,在那里我看到了两条闪存消息:一个:info闪存表示成功注销,另一个:error闪存抱怨“您必须登录才能访问此页面”。我不希望用户在登录页面上看到:error闪存,更糟糕的是,我认为:error闪存出现的原因是因为PageLive liveview (它不在索引页面上)再次运行它的mount/3函数(第三次),这会导致liveview身份验证再次运行,并导致第二次重定向。重要的是,这个问题时断时续地发生,有时重定向工作正常,并将用户发送到索引页,而不存在任何问题,而其他时候,则会显示冗余重定向和错误的闪存消息。我想这表明了某种种族状况。

我有一个相对较新的项目,其中包括这些路线:

router.ex

代码语言:javascript
复制
  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生成的。生成的delete中的UserSessionController操作将触发生成的UserAuth.log_out_user/1

user_session_controller.ex

代码语言:javascript
复制
  def delete(conn, _params) do
    conn
    |> put_flash(:info, "Logged out successfully.")
    |> UserAuth.log_out_user()
  end

user_auth.ex

代码语言:javascript
复制
  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

路由器中的实时路由通过名为PageLive的liveview路由到/dash路由,它只是通过某种身份验证挂载,如liveview文档中所建议的那样。

page_live.ex

代码语言:javascript
复制
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

代码语言:javascript
复制
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

以下是用户单击“注销”后的流程日志:

代码语言:javascript
复制
**[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在注销时重定向到非liveview页面之后再次触发PageLive挂载,从而触发第二次重定向到登录页?

EN

回答 2

Stack Overflow用户

发布于 2021-10-08 11:13:45

他们的关键是这段代码

代码语言:javascript
复制
MyappWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})

log_out_user/1中。在这里,您可以通过Erlang消息断开实时视图套接字。

这将触发套接字关闭服务器端(请参阅Phoenix.Socket)

代码语言:javascript
复制
def __info__(%Broadcast{event: "disconnect"}, state) do
  {:stop, {:shutdown, :disconnected}, state}
end

但是,它是一个活动视图,它将在客户端javascript断开连接后重新连接,然后重新装入活动视图,这将导致MyappWeb.UserLiveAuth再次添加闪存消息。

供参考,检查LiveView文档

一旦断开连接,客户端将尝试重新建立连接并重新执行挂载/3回调。在这种情况下,如果用户不再登录或不再访问当前资源,那么挂载/3将失败,用户将被重定向。

一个潜在的解决方案可能是,在路由管道内的插头中执行闪存+重定向,而不是挂载,这样在加载页面时,非登录用户将被重定向,然后在活动视图挂载中只重定向{:halt, socket},因此在注销时没有重定向。或者,在注销请求已经重定向后发送用于断开连接的广播(可能剥离异步Task可能会有所帮助)。

因此,可以这样包装广播,使浏览器关闭liveview本身(同时仍然重定向所有其他开放的实时视图):

代码语言:javascript
复制
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)
票数 0
EN

Stack Overflow用户

发布于 2021-10-20 19:25:01

user_auth.ex中,我有以下几行,它们从db中删除用户会话:

代码语言:javascript
复制
  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重定向之后才被删除,以避免出现争用情况:

代码语言:javascript
复制
  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

不过,这是一个令人不满意的解决办法,因为我不喜欢手动协调这样的事件。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69410019

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档