Phoenix v1.4.13 Phoenix.Socket.Transport behaviour View Source

Outlines the Socket <-> Transport communication.

This module specifies a behaviour that all sockets must implement. Phoenix.Socket is just one possible implementation of a socket that multiplexes events over multiple channels. Developers can implement their own sockets as long as they implement the behaviour outlined here.

Developers interested in implementing custom transports must invoke the socket API defined in this module. This module also provides many conveniences to make it easier to build custom transports.

Workflow

Whenever your endpoint starts, it will automatically invoke the child_spec/1 on each listed socket and start that specification under the endpoint supervisor. For this reason, custom transports that are manually started in the supervision tree must be listed after the endpoint.

Whenever the transport receives a connection, it should invoke the connect/1 callback with a map of metadata. Different sockets may require different metadatas.

If the connection is accepted, the transport can move the connection to another process, if so desires, or keep using the same process. The process responsible for managing the socket should then call init/1.

For each message received from the client, the transport must call handle_in/2 on the socket. For each informational message the transport receives, it should call handle_info/2 on the socket.

On termination, terminate/2 must be called. A special atom with reason :closed can be used to specify that the client terminated the connection.

Example

Here is a simple pong socket implementation:

defmodule PingSocket do
  @behaviour Phoenix.Socket.Transport

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

  def connect(map) do
    # Callback to retrieve relevant data from the connection.
    # The map contains options, params, transport and endpoint keys.
    {:ok, state}
  end

  def init(state) do
    # Now we are effectively inside the process that maintains the socket.
    {:ok, state}
  end

  def handle_in({"ping", _opts}, state) do
    {:reply, :ok, {:text, "pong"}, state}
  end

  def handle_info(_, state) do
    {:ok, state}
  end

  def terminate(_reason, _state) do
    :ok
  end
end

It can be mounted in your endpoint like any other socket:

socket "/socket", PingSocket, websocket: true, longpoll: true

You can now interact with the socket under /socket/websocket and /socket/longpoll.

Security

This module also provides functions to enable a secure environment on transports that, at some point, have access to a Plug.Conn.

The functionality provided by this module helps in performing "origin" header checks and ensuring only SSL connections are allowed.

Link to this section Summary

Functions

Checks the origin request header against the list of allowed origins.

Checks the Websocket subprotocols request header against the allowed subprotocols.

Runs the code reloader if enabled.

Extracts connection information from conn and returns a map.

Forces SSL in the socket connection.

Logs the transport request.

Callbacks

Returns a child specification for socket management.

Connects to the socket.

Handles incoming socket messages.

Handles info messages.

Initializes the socket state.

Invoked on termination.

Link to this section Types

Link to this section Functions

Link to this function

check_origin(conn, handler, endpoint, opts, sender \\ &Plug.Conn.send_resp/1)

View Source

Checks the origin request header against the list of allowed origins.

Should be called by transports before connecting when appropriate. If the origin header matches the allowed origins, no origin header was sent or no origin was configured, it will return the given connection.

Otherwise a 403 Forbidden response will be sent and the connection halted. It is a noop if the connection has been halted.

Link to this function

check_subprotocols(conn, subprotocols)

View Source

Checks the Websocket subprotocols request header against the allowed subprotocols.

Should be called by transports before connecting when appropriate. If the sec-websocket-protocol header matches the allowed subprotocols, it will put sec-websocket-protocol response header and return the given connection. If no sec-websocket-protocol header was sent it will return the given connection.

Otherwise a 403 Forbidden response will be sent and the connection halted. It is a noop if the connection has been halted.

Link to this function

code_reload(conn, endpoint, opts)

View Source

Runs the code reloader if enabled.

Link to this function

connect_info(conn, endpoint, keys)

View Source

Extracts connection information from conn and returns a map.

Keys are retrieved from the optional transport option :connect_info. This functionality is transport specific. Please refer to your transports' documentation for more information.

The supported keys are:

  • :peer_data - the result of Plug.Conn.get_peer_data/1
  • :x_headers - a list of all request headers that have an "x-" prefix
  • :uri - a %URI{} derived from the conn
Link to this function

force_ssl(conn, socket, endpoint, opts)

View Source

Forces SSL in the socket connection.

Uses the endpoint configuration to decide so. It is a noop if the connection has been halted.

Link to this function

transport_log(conn, level)

View Source

Logs the transport request.

Available for transports that generate a connection.

Link to this section Callbacks

Returns a child specification for socket management.

This is invoked only once per socket regardless of the number of transports and should be responsible for setting up any process structure used exclusively by the socket regardless of transports.

Each socket connection is started by the transport and the process that controls the socket likely belongs to the transport. However, some sockets spawn new processes, such as Phoenix.Socket which spawns channels, and this gives the ability to start a supervision tree associated to the socket.

It receives the socket options from the endpoint, for example:

socket "/my_app", MyApp.Socket, shutdown: 5000

means child_spec([shutdown: 5000]) will be invoked.

Link to this callback

connect(transport_info)

View Source
connect(transport_info :: map()) :: {:ok, state()} | :error

Connects to the socket.

The transport passes a map of metadata and the socket returns {:ok, state} or :error. The state must be stored by the transport and returned in all future operations.

This function is used for authorization purposes and it may be invoked outside of the process that effectively runs the socket.

In the default Phoenix.Socket implementation, the metadata expects the following keys:

  • :endpoint - the application endpoint
  • :transport - the transport name
  • :params - the connection parameters
  • :options - a keyword list of transport options, often given by developers when configuring the transport. It must include a :serializer field with the list of serializers and their requirements
Link to this callback

handle_in({}, state)

View Source
handle_in({message :: term(), opts :: keyword()}, state()) ::
  {:ok, state()}
  | {:reply, :ok | :error, {opcode :: atom(), message :: term()}, state()}
  | {:stop, reason :: term(), state()}

Handles incoming socket messages.

The message is represented as {payload, options}. It must return one of:

  • {:ok, state} - continues the socket with no reply
  • {:reply, status, reply, state} - continues the socket with reply
  • {:stop, reason, state} - stops the socket

The reply is a tuple contain an opcode atom and a message that can be any term. The built-in websocket transport supports both :text and :binary opcode and the message must be always iodata. Long polling only supports text opcode.

Link to this callback

handle_info(message, state)

View Source
handle_info(message :: term(), state()) ::
  {:ok, state()}
  | {:push, {opcode :: atom(), message :: term()}, state()}
  | {:stop, reason :: term(), state()}

Handles info messages.

The message is a term. It must return one of:

  • {:ok, state} - continues the socket with no reply
  • {:push, reply, state} - continues the socket with reply
  • {:stop, reason, state} - stops the socket

The reply is a tuple contain an opcode atom and a message that can be any term. The built-in websocket transport supports both :text and :binary opcode and the message must be always iodata. Long polling only supports text opcode.

Link to this callback

init(state)

View Source
init(state()) :: {:ok, state()}

Initializes the socket state.

This must be executed from the process that will effectively operate the socket.

Link to this callback

terminate(reason, state)

View Source
terminate(reason :: term(), state()) :: :ok

Invoked on termination.

If reason is :closed, it means the client closed the socket.