Skip to content

Commit

Permalink
Version transport contract. Closes phoenixframework#1004
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismccord committed Aug 19, 2015
1 parent 6097f1e commit fbf38ee
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 10 deletions.
25 changes: 25 additions & 0 deletions lib/phoenix/socket/transport.ex
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ defmodule Phoenix.Socket.Transport do
However, a client can be implemented for other protocols and encodings by
abiding by the `Phoenix.Socket.Message` format.
## Protocol Versioning
Clients are expected to send the Channel Transport protocol version that they
expect to be talking to. The version can be retrieved on the server from
`Phoenix.Channel.Transport.protocol_version/0`. If no version is provided, the
Transport adapters should default to assume a `"1.0.0"` version number.
See `web/static/js/phoenix.js` for an example transport client
implementation.
"""
Expand All @@ -101,6 +107,9 @@ defmodule Phoenix.Socket.Transport do
alias Phoenix.Socket.Message
alias Phoenix.Socket.Reply

@protocol_version "1.0.0"
@client_vsn_requirements "~> 1.0"

@doc """
Provides a keyword list of default configuration for socket transports.
"""
Expand All @@ -111,6 +120,11 @@ defmodule Phoenix.Socket.Transport do
"""
defcallback handlers() :: map

@doc """
Returns the Channel Transport protocol version.
"""
def protocol_version, do: @protocol_version

@doc """
Handles the socket connection.
Expand All @@ -121,6 +135,17 @@ defmodule Phoenix.Socket.Transport do
topic from the `id/1` callback.
"""
def connect(endpoint, handler, transport_name, transport, serializer, params) do
vsn = params["vsn"] || "1.0.0"

if Version.match?(vsn, @client_vsn_requirements) do
connect_vsn(endpoint, handler, transport_name, transport, serializer, params)
else
Logger.error "The client's requested channel transport version \"#{vsn}\" " <>
"does not match server's version requirements of \"#{@client_vsn_requirements}\""
:error
end
end
defp connect_vsn(endpoint, handler, transport_name, transport, serializer, params) do
socket = %Socket{endpoint: endpoint,
transport: transport,
transport_pid: self(),
Expand Down
8 changes: 3 additions & 5 deletions priv/static/phoenix.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ var _classCallCheck = function (instance, Constructor) { if (!(instance instance
// `channel.leave()`
//

var VSN = "1.0.0";
var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
var CHANNEL_STATES = {
closed: "closed",
Expand Down Expand Up @@ -602,7 +603,7 @@ var Socket = exports.Socket = (function () {
},
endPointURL: {
value: function endPointURL() {
var uri = Ajax.appendParams(this.endPoint, this.params);
var uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params), { vsn: VSN });
if (uri.charAt(0) !== "/") {
return uri;
}
Expand Down Expand Up @@ -913,10 +914,7 @@ var LongPoll = exports.LongPoll = (function () {
},
endpointURL: {
value: function endpointURL() {
return Ajax.appendParams(this.pollEndpoint, {
token: this.token,
format: "json"
});
return Ajax.appendParams(this.pollEndpoint, { token: this.token });
},
writable: true,
configurable: true
Expand Down
8 changes: 8 additions & 0 deletions test/phoenix/integration/long_poll_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,12 @@ defmodule Phoenix.Integration.LongPollTest do
poll(:get, "/ws", session)
assert resp.body["status"] == 410
end

test "refuses non-matching versions" do
log = capture_log fn ->
resp = poll(:get, "/ws", %{vsn: "123.1.1"}, nil, %{"origin" => "https://example.com"})
assert resp.body["status"] == 403
end
assert log =~ "The client's requested channel transport version \"123.1.1\" does not match server's version"
end
end
9 changes: 9 additions & 0 deletions test/phoenix/integration/websocket_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,13 @@ defmodule Phoenix.Integration.WebSocketTest do
end
assert log =~ "Phoenix.Integration.WebSocketTest.RoomChannel received join event with topic \"rooms:joiner\" but channel already joined"
end

test "returns 403 when versions to not match" do
log = capture_log fn ->
url = "ws://127.0.0.1:#{@port}/ws/websocket?vsn=123.1.1"
assert WebsocketClient.start_link(self, url) ==
{:error, {403, "Forbidden"}}
end
assert log =~ "The client's requested channel transport version \"123.1.1\" does not match server's version"
end
end
4 changes: 4 additions & 0 deletions test/phoenix/transports/transport_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,8 @@ defmodule Phoenix.Transports.TransportTest do
conn = Transport.force_ssl(conn(:get, "https://foo.com/"), make_ref(), Endpoint, [])
refute conn.halted
end

test "provides the protocol version" do
assert Version.match?(Transport.protocol_version(), "~> 1.0")
end
end
9 changes: 4 additions & 5 deletions web/static/js/phoenix.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
// `channel.leave()`
//

const VSN = "1.0.0"
const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3}
const CHANNEL_STATES = {
closed: "closed",
Expand Down Expand Up @@ -356,7 +357,8 @@ export class Socket {
protocol(){ return location.protocol.match(/^https/) ? "wss" : "ws" }

endPointURL(){
let uri = Ajax.appendParams(this.endPoint, this.params)
let uri = Ajax.appendParams(
Ajax.appendParams(this.endPoint, this.params), {vsn: VSN})
if(uri.charAt(0) !== "/"){ return uri }
if(uri.charAt(1) === "/"){ return `${this.protocol()}:${uri}` }

Expand Down Expand Up @@ -514,10 +516,7 @@ export class LongPoll {
}

endpointURL(){
return Ajax.appendParams(this.pollEndpoint, {
token: this.token,
format: "json"
})
return Ajax.appendParams(this.pollEndpoint, {token: this.token})
}

closeAndRetry(){
Expand Down

0 comments on commit fbf38ee

Please sign in to comment.