【问题标题】:Phoenix Channel can't maintain connection with frontend once the connection is established (Error during WebSocket handshake)建立连接后,Phoenix Channel 无法与前端保持连接(WebSocket 握手期间出错)
【发布时间】:2020-11-04 06:17:57
【问题描述】:

我的 WebSocket 连接有问题。

我使用 Phoenix 作为我的 API,在前端使用 Vue + phoenix-socket。

我的浏览器控制台如下所示:

receive: ok feed:1 phx_reply (1) {response: {…}, status: "ok"}

Joined successfully {feed: Array(3)}

WebSocket connection to 'ws://localhost:4000/socket/websocket?vsn=1.0.0' failed: Error during WebSocket handshake: Unexpected response code: 403

push: phoenix heartbeat (2) {}

receive: ok phoenix phx_reply (2) {response: {…}, status: "ok"}

WebSocket connection to 'ws://localhost:4000/socket/websocket?vsn=1.0.0' failed: Error during WebSocket handshake: Unexpected response code: 403

等等……

如您所见,可以建立连接并且数据通过,但随后会发送错误。

所以我检查了凤凰:

[info] CONNECTED TO TweeterApiWeb.UserSocket in 0┬Ás
  Transport: :websocket
  Serializer: Phoenix.Socket.V1.JSONSerializer
  Parameters: %{"token" => "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0d2VldGVyX2FwaSIsImV4cCI6MTU5NjkxMjAxNiwiaWF0IjoxNTk0NDkyODE2LCJpc3MiOiJ0d2VldGVyX2FwaSIsImp0aSI6IjViYWFlMDRlLTBjMTYtNDEyMi05Y2VlLWZmMzQ2OWM1YWE1YiIsIm5iZiI6MTU5NDQ5MjgxNSwic3ViIjoiMSIsInR5cCI6ImFjY2VzcyJ9.-ZJMyyEBKd0_nHYUBGdaI0qdHn1nuWtpG8sEUHqikBuWTB2sKw9Sk36OsUpXBS5ozRpe2l2VXq8NI58HydIhZA", "vsn" => "1.0.0"}
[debug] QUERY OK source="tweets" db=0.0ms idle=875.0ms
SELECT t0."id", t0."content", t0."comment_count", t0."retweet_count", t0."like_count", t0."profile_id", t0."inserted_at", t0."updated_at" FROM "tweets" AS t0 WHERE (t0."profile_id" = $1) [1]
[info] JOINED feed:1 in 0┬Ás
  Parameters: %{}
[info] REFUSED CONNECTION TO TweeterApiWeb.UserSocket in 0┬Ás
  Transport: :websocket
  Serializer: Phoenix.Socket.V1.JSONSerializer
  Parameters: %{"vsn" => "1.0.0"}

似乎连接被拒绝,因为 params 中没有令牌,但我真的不明白为什么。

我只在套接字连接时检查身份验证,因此一旦建立连接,令牌应该是完全不需要的。

这是我的 user_socket.ex:

defmodule TweeterApiWeb.UserSocket do
  use Phoenix.Socket

  alias TweeterApi.Accounts
  alias TweeterApi.Accounts.Guardian

  ## Channels
  channel "feed:*", TweeterApiWeb.FeedChannel

  @impl true
  def connect(%{"token" => token}, socket, _connect_info) do
    case Guardian.resource_from_token(token) do
      {:ok, user, _claims} ->
        current_profile = Accounts.get_profile_by(user_id: user.id)

        {:ok, assign(socket, :current_profile_id, current_profile.id)}

      {:error, _reason} ->
        :error
    end
  end

  def connect(_params, _socket, _connect_info), do: :error

  @impl true
  def id(socket), do: "users_socket:#{socket.assigns.current_profile_id}"
end

频道代码:

defmodule TweeterApiWeb.FeedChannel do
  use TweeterApiWeb, :channel

  alias TweeterApi.Tweets

  def join("feed:" <> current_profile_id, _params, socket) do
    if String.to_integer(current_profile_id) === socket.assigns.current_profile_id do
      current_profile_tweets = Tweets.list_profile_tweets(current_profile_id)

      response = %{
        feed:
          Phoenix.View.render_many(current_profile_tweets, TweeterApiWeb.TweetView, "tweet.json")
      }

      {:ok, response, socket}
    else
      {:error, %{reson: "Not authorized"}}
    end
  end

  def terminate(_reason, socket) do
    {:ok, socket}
  end
end

和 Vue.js 代码:

<script>
import UserProfileSection from '@/components/sections/UserProfileSection.vue'
import TimelineSection from '@/components/sections/TimelineSection.vue'
import FollowPropositionsSection from '@/components/sections/FollowPropositionsSection.vue'
import NewTweetForm from '@/components/sections/NewTweetForm.vue'
import { mapGetters } from 'vuex'
import { Socket } from 'phoenix-socket'

export default {
    name: 'AppFeed',
    components: {
        UserProfileSection,
        TimelineSection,
        FollowPropositionsSection,
        NewTweetForm,
    },
    data() {
        return {
            tweets: [],
        }
    },
    computed: {
        ...mapGetters('auth', ['currentProfileId', 'token']),
        ...mapGetters('feed', ['tweets'])
    },
    created() {

    },
    mounted() {
        const WEBSOCKET_URL = 'ws://localhost:4000'

        const socket = new Socket(`${WEBSOCKET_URL}/socket`, {
            params: { token: this.token },
            logger: (kind, msg, data) => {
                console.log(`${kind}: ${msg}`, data)
            },
        })

        socket.connect()

        this.channel = socket.channel('feed:' + this.currentProfileId, {})

        this.channel
            .join()
            .receive('ok', (resp) => {
                console.log('Joined successfully', resp)
                console.log(resp)
                this.tweets = resp.feed
            })
            .receive('error', (resp) => {
                console.log('Unable to join', resp)
            })
    }
}
</script>

【问题讨论】:

    标签: vue.js websocket elixir phoenix-framework phoenix-channels


    【解决方案1】:

    在典型的 phoenix 应用程序中,将用户的令牌分配给我们布局主体中的窗口。

    <script>window.userToken = "<%= assigns[:user_token] %>"</script>
    

    以及在 assets/js/socket.js 中创建套接字

    let socket = new Socket("/socket", {
      params: {token: window.userToken},
    })
    

    我相信当你创建你的套接字时:{ token: this.token },this.token 是未定义的,所以它不会在 params 中发送。

    编辑: 如果您不关心令牌,请不要对其进行模式匹配。如果没有令牌,您将跳入您的第二个连接,该连接将匹配任何内容并拒绝连接。

    【讨论】:

      猜你喜欢
      • 2017-05-09
      • 2019-05-16
      • 2016-11-05
      • 1970-01-01
      • 1970-01-01
      • 2017-02-12
      • 2021-10-06
      • 1970-01-01
      • 2018-06-04
      相关资源
      最近更新 更多