js new websocket new未定义_在phoenix 1.4中使用原始websocket

30fd25ae1d2aecf1b223fb8c91ba750f.png

cowboy

本文讨论只cowboy版本2.0以上。

cowboy本身是支持websocket的。其路由配置(cowboy称之为dispatch)中每个路由对应一个handler

handler指一个回调模块,要求init(req, state)

对于HTTP请求,init/2应该向req发送请求并返回{:ok, new_req, new_state}

对于websocket请求(建立websocket的HTTP请求),init/2应该返回{:cowboy_websocket, new_req, new_state}指示handler本身也是一个websocket回调模块,实现了websocket_init/1(可选)、websocket_handle/2websocket_info/2terminate/3(可选)。

plug

plug自身有一套路由配置,所有路由共同组成一个Plug

为了使用plug自身的路由配置,plug_cowboy并没有使用cowboy的路由配置,而是直接配置一个通配路由,handlerPlug.Cowboy.HandlerPlug.Cowboy.Handler中再调用顶层Plug

遗憾的是Plug.Cowboy.Handler一定会返回{:ok, new_req, new_state},它不能用于websocket。

因此,如果想要在plug项目中使用原始websocket,必须自行指定cowboy的dispatch,并将最后一个通配路由指定到Plug.Cowboy.Handler

top_plug_opts = MyApp.TopPlug.init(key1: value1)
cowboy_opts = [
  port: 4040,
  dispatch: [{:_, [
    {"/ws01", MyApp.WebsocketHandler01, []},
    {"/ws02", MyApp.WebsocketHandler02, []},
    {:_, Plug.Cowboy.Handler, {MyApp.TopPlug, top_plug_opts}},
  ]}],
]

# 用 Elixir 1.5+ 的 child_spec/1 插入到 supervisor tree 中
children = [
  {Plug.Cowboy, scheme: :http, plug: {MyApp.TopPlug, key1: :now_available}, cowboy_opts}
]
Supervisor.start_link(children, strategy: :one_for_one)

# 手动启动
Plug.Cowboy.http MyApp.TopPlug, [key1: :now_available], cowboy_opts

注意Plug.Cowboy.Handler的初始state;传给顶层Plug的初始化参数必须自行处理。

phoenix < 1.4

phoenix为了实现其channel功能,使用了上述方法实现了websocket。其顶层Plug被phoenix称为endpoint

然而,老版本phoenix为websocket提供的handler与其channel功能耦合严重,这个handler会直接用channel的序列化格式处理websocket消息,然后再传递给用户的回调模块。

如果想要支持原生websocket,依然需要自己定义handler并手动指定dispatch

dispatch除了自定义的handler和通配项,还要把所有channel的websocket一起包含,甚至开发环境下还需要要把live reloader的websocket也包含进去。

phoenix >= 1.4

1.4版本开始,phoenix切换到了cowboy的2.0+版本。

这个版本中,phoenix重写了websocket用的handler,websocket消息会直接发往用户的回调模块,channel的序列化格式改到用户的回调模块处理。

因此使用原始websocket变得更加方便。在endpoint中:

socket "/socket1", MyApp.WebsocketHandler01,
  longpoll: false,
  websocket: [
    path: "",
    connect_info: [key1: :value1],
  ]

这里的path参数很重要,用于防止phoenix在我们指定的路由后面加上后缀。

如果MyApp.WebsocketHandler01use Phoenix.Socket,则是正常的channel用的回调模块。

如果不use Phoenix.Socket,就可以直接处理websocket消息了:

defmodule MyApp.WebsocketHandler01 do
  require Logger
  @behaviour Phoenix.Socket.Transport

  @impl true
  def child_spec(_opts) do
    # We won't spawn any process, so let's return a dummy task
    %{
      id: __MODULE__,
      start: {Task, :start_link, [fn -> :ok end]},
      restart: :transient
    }
  end

  @impl true
  def connect(transport_info) do
    # value1 = transport_info.connect_info.key1
    # value1 = transport_info.options[:connect_info][:key1]
    Logger.info "Websocket connects: #{inspect transport_info}"
    init_state = []
    {:ok, init_state}
  end

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_in({message, opts}, state) do
    case opts[:opcode] do
    :text ->
      Logger.info "Receive websocket text message: #{inspect message}"
      {:reply, :ok, {:text, message}, state}
    :binary ->
      Logger.info "Receive websocket binary message: #{inspect message}"
      {:reply, :ok, {:binary, message}, state}
    opcode ->
      reason = "Cannot handle input message with opcode: #{inspect opcode}"
      Logger.warn reason
      {:stop, reason, state}
    end
  end

  @impl true
  def handle_info(message, state) do
    Logger.info "Receive process message: #{inspect message}"
    {:ok, state}
  end

  @impl true
  def terminate(reason, _state) do
    Logger.info "Websocket closed: #{inspect reason}"
    :ok
  end

end

这个模块已经基本做到了和channel解耦,只剩下connect的参数中带有一些channel相关的参数,但只要注意不与phoenix使用的参数冲突,就可以传入初始化参数:socket/3

如果不喜欢Phoenix.Socket.Transport封装过的回调,依然想要自定义handler,那只能沿用用1.4版本以下的老办法了:Phoenix.Endpoint.Cowboy2Adapter

只不过这个版本中,自定义dispatch不用再考虑channel和live reloader用的websocket了,这个版本中phoenix干脆连plug的handler也不用了,直接将websocekt和HTTP的路由配置整合,将handler实现为两用的。socket/3定义的websocket(包括channel用的和自定义的Phoenix.Socket.Transport回调模块)移到了handler中处理,不用再填到dispatch中了。

如果不使用phoenix的channel,可以在配置中把endpointpubsub参数去掉,对应的supervisor tree便不会启动,同时不影响上述原始websocket的使用。


版权声明:本文为weixin_29864813原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。