Phoenix LiveView登出跳转非LiveView页面时重复触发mount问题
问题成因
这确实是竞态条件导致的,触发逻辑如下:
- 浏览器发起
DELETE /users/log_out请求,服务端开始执行登出逻辑 - 服务端先删除了数据库里的用户session token,接着通过PubSub向当前用户的LiveSocket广播
disconnect消息 - 这个PubSub消息是进程内/本地广播,几乎瞬时到达浏览器端的LiveSocket客户端
- LiveSocket默认断开后会自动重连,此时浏览器还没收到服务端返回的302跳转响应,旧的
/dashPageLive页面还没被销毁,所以重连请求会携带旧的session信息尝试重新mount PageLive - 此时数据库里的token已经被删除,UserLiveAuth的mount逻辑认证失败,触发重定向到登录页
- 等到浏览器收到302跳转响应的时候,页面已经被LiveView的客户端重定向到登录页了,就出现了你看到的错误提示。
如果302响应的网络传输比PubSub消息处理更快,浏览器先跳转页面销毁了socket,就不会触发重连,所以这个问题是偶现的。
解决方案
推荐两个稳妥的修复方式,选其中一种即可:
方案1:调整登出逻辑的执行顺序,优先返回重定向
把销毁token、广播断开的逻辑放到异步任务中执行,让302响应先返回给浏览器,避免重连:
修改user_auth.ex的log_out_user/1方法:
def log_out_user(conn) do user_token = get_session(conn, :user_token) live_socket_id = get_session(conn, :live_socket_id) # 异步执行销毁操作,不阻塞重定向响应返回 Task.start(fn -> user_token && Accounts.delete_session_token(user_token) if live_socket_id, do: MyappWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{}) end) conn |> renew_session() |> delete_resp_cookie(@remember_me_cookie) |> redirect(to: "/") end
方案2:禁用LiveSocket主动断开后的自动重连
修改前端LiveSocket配置(assets/js/app.js),收到服务端主动断开的消息时不重试,直接跳转首页:
let liveSocket = new LiveSocket("/live", Socket, { params: {_csrf_token: csrfToken}, onDisconnect: (code, reason) => { // 服务端主动广播的disconnect消息,reason为"disconnected" if (reason === "disconnected") { // 直接跳转首页,不触发重连 window.location.href = "/" } } })
两种方案都可以彻底解决这个竞态问题,方案1改后端逻辑更简单,不需要动前端代码,更推荐使用。
内容的提问来源于stack exchange,提问作者vaer-k
相关产品推荐
相关产品推荐

