Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test that websockets work through the master proxy to a phoenix endpo…
…int.
- Loading branch information
1 parent
5f5f3f5
commit 55ac62d
Showing
5 changed files
with
218 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# Copied from https://raw.githubusercontent.com/phoenixframework/phoenix/v1.4/test/support/websocket_client.exs | ||
defmodule MasterProxy.Integration.WebsocketClient do | ||
alias Phoenix.Socket.Message | ||
require Logger | ||
|
||
@doc """ | ||
Starts the WebSocket server for given ws URL. Received Socket.Message's | ||
are forwarded to the sender pid | ||
""" | ||
def start_link(sender, url, serializer, headers \\ []) do | ||
:crypto.start() | ||
:ssl.start() | ||
|
||
Logger.debug inspect url | ||
:websocket_client.start_link( | ||
String.to_charlist(url), | ||
__MODULE__, | ||
[sender, serializer], | ||
extra_headers: headers | ||
) | ||
end | ||
|
||
@doc """ | ||
Closes the socket | ||
""" | ||
def close(socket) do | ||
send(socket, :close) | ||
end | ||
|
||
@doc """ | ||
Sends an event to the WebSocket server per the message protocol. | ||
""" | ||
def send_event(server_pid, topic, event, msg) do | ||
send(server_pid, {:send, %Message{topic: topic, event: event, payload: msg}}) | ||
end | ||
|
||
@doc """ | ||
Sends a low-level text message to the client. | ||
""" | ||
def send_message(server_pid, msg) do | ||
send(server_pid, {:send, msg}) | ||
end | ||
|
||
@doc """ | ||
Sends a heartbeat event | ||
""" | ||
def send_heartbeat(server_pid) do | ||
send_event(server_pid, "phoenix", "heartbeat", %{}) | ||
end | ||
|
||
@doc """ | ||
Sends join event to the WebSocket server per the Message protocol | ||
""" | ||
def join(server_pid, topic, msg) do | ||
send_event(server_pid, topic, "phx_join", msg) | ||
end | ||
|
||
@doc """ | ||
Sends leave event to the WebSocket server per the Message protocol | ||
""" | ||
def leave(server_pid, topic, msg) do | ||
send_event(server_pid, topic, "phx_leave", msg) | ||
end | ||
|
||
@doc false | ||
def init([sender, serializer], _conn_state) do | ||
{:ok, %{sender: sender, join_ref: 1, ref: 0, serializer: serializer}} | ||
end | ||
|
||
@doc false | ||
def websocket_handle({:text, msg}, _conn_state, %{serializer: :noop} = state) do | ||
send(state.sender, {:text, msg}) | ||
{:ok, state} | ||
end | ||
|
||
def websocket_handle({:text, msg}, _conn_state, state) do | ||
send(state.sender, state.serializer.decode!(msg, opcode: :text)) | ||
{:ok, state} | ||
end | ||
|
||
@doc false | ||
def websocket_info({:send, msg}, _conn_state, %{serializer: :noop} = state) do | ||
{:reply, {:text, msg}, state} | ||
end | ||
|
||
def websocket_info({:send, %Message{} = msg}, _conn_state, state) do | ||
msg = Map.merge(msg, %{ref: to_string(state.ref + 1), join_ref: to_string(state.join_ref)}) | ||
{:reply, {:text, encode!(msg, state)}, put_in(state, [:ref], state.ref + 1)} | ||
end | ||
|
||
def websocket_info(:close, _conn_state, _state) do | ||
{:close, <<>>, "done"} | ||
end | ||
|
||
@doc false | ||
def websocket_terminate(_reason, _conn_state, _state) do | ||
:ok | ||
end | ||
|
||
defp encode!(map, state) do | ||
{:socket_push, :text, chardata} = state.serializer.encode!(map) | ||
IO.chardata_to_string(chardata) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# Copied from https://raw.githubusercontent.com/phoenixframework/phoenix/v1.4/test/phoenix/integration/websocket_socket_test.exs | ||
Code.require_file("./support/websocket_client.exs", __DIR__) | ||
|
||
defmodule MasterProxy.Integration.WebSocketTest do | ||
use ExUnit.Case, async: true | ||
import ExUnit.CaptureLog | ||
require Logger | ||
|
||
alias MasterProxy.Integration.WebsocketClient | ||
alias __MODULE__.Endpoint | ||
|
||
@moduletag :capture_log | ||
@port 5907 | ||
@path "ws://127.0.0.1:#{@port}/ws/websocket" | ||
|
||
# TODO: how does this work? when I try to configure | ||
# :master_proxy here, it is too late. maybe ExUnit is | ||
# starting my "main" app automatically before we get here? | ||
Application.put_env( | ||
:phoenix, | ||
Endpoint, | ||
https: false, | ||
http: [port: @port + 1], | ||
debug_errors: false, | ||
server: true | ||
) | ||
|
||
defmodule UserSocket do | ||
@behaviour Phoenix.Socket.Transport | ||
|
||
def child_spec(opts) do | ||
:value = Keyword.fetch!(opts, :custom) | ||
Supervisor.Spec.worker(Task, [fn -> :ok end], restart: :transient) | ||
end | ||
|
||
def connect(map) do | ||
%{endpoint: Endpoint, params: params, transport: :websocket} = map | ||
{:ok, {:params, params}} | ||
end | ||
|
||
def init({:params, _} = state) do | ||
{:ok, state} | ||
end | ||
|
||
def handle_in({"params", opts}, {:params, params} = state) do | ||
:text = Keyword.fetch!(opts, :opcode) | ||
{:reply, :ok, {:text, inspect(params)}, state} | ||
end | ||
|
||
def handle_in({"ping", opts}, state) do | ||
:text = Keyword.fetch!(opts, :opcode) | ||
send(self(), :ping) | ||
{:ok, state} | ||
end | ||
|
||
def handle_info(:ping, state) do | ||
{:push, {:text, "pong"}, state} | ||
end | ||
|
||
def terminate(_reason, {:params, _}) do | ||
:ok | ||
end | ||
end | ||
|
||
defmodule Endpoint do | ||
use Phoenix.Endpoint, otp_app: :phoenix | ||
|
||
socket "/ws", UserSocket, | ||
websocket: [check_origin: ["//example.com"], timeout: 200], | ||
custom: :value | ||
end | ||
|
||
setup_all do | ||
# capture_log(fn -> MasterProxy.Application.start(nil, nil) end) | ||
# MasterProxy.Application.start(nil, nil) | ||
# matches everything and proxies over to the Endpoint here | ||
backends = [%{phoenix_endpoint: Endpoint}] | ||
Application.put_env(:master_proxy, :backends, backends) | ||
# This needs to start so Phoenix.Config is initialized | ||
# among other things | ||
capture_log(fn -> Endpoint.start_link() end) | ||
:ok | ||
end | ||
|
||
test "refuses unallowed origins" do | ||
capture_log(fn -> | ||
headers = [{"origin", "https://example.com"}] | ||
assert {:ok, _} = WebsocketClient.start_link(self(), @path, :noop, headers) | ||
|
||
headers = [{"origin", "http://notallowed.com"}] | ||
assert {:error, {403, _}} = WebsocketClient.start_link(self(), @path, :noop, headers) | ||
end) | ||
end | ||
|
||
test "returns params with sync request" do | ||
assert {:ok, client} = WebsocketClient.start_link(self(), "#{@path}?key=value", :noop) | ||
WebsocketClient.send_message(client, "params") | ||
assert_receive {:text, ~s(%{"key" => "value"})} | ||
end | ||
|
||
test "returns pong from async request" do | ||
assert {:ok, client} = WebsocketClient.start_link(self(), "#{@path}?key=value", :noop) | ||
WebsocketClient.send_message(client, "ping") | ||
assert_receive {:text, "pong"} | ||
end | ||
end |