Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Christopher Faulet
532 lines (501 sloc) 19.651 kb
<erl>
out(A) ->
{ssi, "TAB.inc", "%%",[{"websockets", "choosen"}]}.
</erl>
<div id="entry">
<h1>WebSockets in Yaws</h1>
<p>
WebSockets! The new kid in town! Joe
<a href="http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html">loves it</a>,
maybe you should too?
</p>
<p>
WebSockets allow for *real* two-way communication between the browser and
Yaws without the overhead and latency that come with polling/long-polling
solutions. That should be enough for an introduction. Now... how to use it?
</p>
<h3>A simple example</h3>
<p>
First of all, here is a simple example. It shows how to upgrade connections
from HTTP to WebSocket.
</p>
<div class="box">
<pre>
<erl>
out(A) ->
{ssi, "websockets_example_endpoint.yaws",[],[]}.
</erl>
</pre>
</div>
<p>
The above code can be executed <a href="websockets_example.yaws">Here</a>.
</p>
<h3>Establish a WebSocket connection</h3>
<p>
To establish a WebSocket connection, a client must send a valid HTTP
Upgrade request. Then, from the server side, the Yaws script (or the appmod
or whatever) should return:
</p>
<div class="box">
<pre>
{websocket, CallbackMod, Options}
</pre>
</div>
<p>
where <code>CallbackMod</code> is an atom identifying the WebSocket callback
module, and <code>Options</code> is a (possibly empty) list (see below for
details).
</p>
<p>
From here, Yaws spawns an Erlang process to manage the WebSocket
connection. Once the handshake response is returned by Yaws, the connection
is established and the handling process is ready to send or receive data. If
something goes wrong during this step, Yaws returns an HTTP error (400, 403
or 500 depending of the error type).
</p>
<h4>Supported Options</h4>
<p>
The following options are available:
<ul>
<li>
<code>{callback, CallbackType}</code>
<p>
Specify the type of the callback module. <code>CallbackType</code> can
be either of the following:
<ul>
<li style="margin-bottom: 10px;">
<code>basic</code> - Same as <code>{basic, []}</code>. This is the
default.
</li>
<li style="margin-bottom: 10px;">
<code>{basic, InitialState}</code> - Indicate your callback module
is a basic callback module. <code>InitialState</code> is the
callback's initial state for handling this client.
</li>
<li style="margin-bottom: 10px;">
<code>{advanced, InitialState}</code> - Same as above but for an
advanced callback module.
</li>
</ul>
</p>
</li>
<li>
<code>{origin, Origin}</code>
<p>
Specify the <code>Origin</code> URL from which messages will be
accepted. This is useful for protecting against cross-site attack. The
option defaults to <code>any</code>, meaning calls will be accepted
from any origin.
</p>
</li>
<li>
<code>{keepalive, KeepAliveBoolean}</code>
<p>
If true, Yaws will automatically send a ping message
every <code>keepAliveTimeout</code> milliseconds. By default keepalive
pings are disabled.
</p>
</li>
<li>
<code>{keepalive_timeout, keepAliveTimeout}</code>
<p>
Specify the interval in milliseconds to send keepalive pings, by
default 30000. Ignored if <code>KeepAliveBoolean</code> is false.
</p>
</li>
<li>
<code>{keepalive_grace_period, KeepAliveGracePeriod}</code>
<p>
Specify the amount of time, in milliseconds, to wait after sending a
keepalive ping. If no message is received
within <code>KeepAliveGracePeriod</code> milliseconds, a timeout will
occur. Depending on the <code>DropBoolean</code> value, a close frame
is sent with the status code 1006 (if <code>DropBoolean</code> is
true) or the callback module is notified
(see <code>Module:handle_info/2</code> below). <br/> By
default, <code>KeepAliveGracePeriod</code> is set to 2000. Ignored
if <code>KeepAliveBoolean</code> is false.
</p>
</li>
<li>
<code>{drop_on_timeout, DropBoolean}</code>
<p>
If true, a close frame is sent with the status code 1006 when a
timeout occurs after a keepalive ping has been sent
(see <code>KeepAliveGracePeriod</code>). Disabled by default. Ignored
if <code>KeepAliveBoolean</code> is false.
</p>
</li>
<li>
<code>{close_timeout, CloseTimeout}</code>
<p>
After sending a close frame to a client, Yaws will wait for the client
acknowledgement for <code>CloseTimeout</code> milliseconds. Then it
will close the underlying TCP connection. By
default <code>CloseTimeout</code> is set to 5000.
</p>
</li>
<li>
<code>{close_if_unmasked, CloseUnmaskedBoolean}</code>
<p>
If true, Yaws will reject any unmasked incoming frame by sending a
close frame with the status code 1002. Disabled by default.<br/>
Note: According to RFC 6455, a client must mask all frames that it
sends to the server
(See <a href="https://tools.ietf.org/html/rfc6455#section-5.1">RFC
6455 - Section 5.1</a>).
</p>
</li>
<li>
<code>{max_frame_size, MaxFrameSize}</code>
<p>
Specify the maximum allowed size, in bytes, for received frames. By
default 16MB. It is also the maximum size for unfragmented
messages.<br/> This limit is checked for all types of callback module.
</p>
</li>
<li>
<code>{max_message_size, MaxMsgSize}</code>
<p>
Specify the maximum allowed message size in bytes, by default
16MB.<br/> This limit is checked only for basic callback modules.
</p>
</li>
<li>
<code>{auto_fragment_message, AutoFragBoolean}</code>
<p>
If true, outgoing messages will be automatically fragmented if their
payload exceeds <code>OutFragSize</code> bytes. This flag is set to
false by default.
</p>
</li>
<li>
<code>{auto_fragment_threshold, OutFragSize}</code>
<p>
Specify the maximum payload size of each fragment
if <code>AutoFragBoolean</code> is true. <code>OutFragSize</code> is
set to 1MB by default. Ignored is <code>AutoFragBoolean</code> is
false.
</p>
</li>
</ul>
</p>
<h3>WebSocket callback modules</h3>
<p>
All frames received on a WebSocket connection are passed to the callback
modules specified during the connection establishment by
calling <code>Module:handle_message/1</code>
or <code>Module:handle_message/2</code>, depending on whether it’s a basic
or an advanced callback module.
</p>
<h4>Basic callback modules</h4>
<p>
When a basic callback module is used, the messages defragmentation is
handled by Yaws. From the callback module point of view, all incoming
messages are unfragmented. This implies that fragmented frames will be
accumulated, thus basic callback modules does not support data streaming.
</p>
<p>
A basic callback module <strong>MUST</strong> define the stateless
function <code>Module:handle_message/1</code>:
</p>
<blockquote>
<strong>Module:handle_message(Message) -> Result</strong>
<pre>
Message :: {Type, Data} | {close, Status, Reason}
Result :: noreply | {reply, Reply} | {close, CloseReason}
Type :: text | binary
Data :: binary()
Reply :: {Type, Data} | #ws_frame{} |
[{Type, Data}] | [#ws_frame{}]
CloseReason :: Status | {Status, Reason}
Status :: integer() %% RFC 6455 Status Code
Reason :: binary()
</pre>
<p>
This function is called when a message is received. <code>{text,
Data}</code> (or <code>{binary, Data}</code>) is the unfragmented text (or
binary) message. When the client closes the connection, the callback
module is notified with the message <code>{close, Status, Reason}</code>,
where <code>Status</code> is the numerical status code sent by the client
or the value 1000
(see <a href="http://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455 -
Section 7.4.1</a>) if the client sent no status code. For an abnormal
client closure, the status code is 1006 (as specified
by <a href="http://tools.ietf.org/html/rfc6455#section-7.1.5">RFC 6455 -
Section 7.1.5</a>). <code>Reason</code> is a binary containing any text
the client sent to indicate the reason for closing the socket; this binary
may be empty.
</p>
<p>
If the function returns <code>{reply, Reply}</code>, <code>Reply</code> is
sent to the client. It is possible to send one or more unfragmentated
messages by returning <code>{Type, Data}</code> or <code>[{Type,
Data}]</code>. It is also possible to send one or more frames
using the <code>#ws_frame{}</code> record instead, defined
in <code>include/yaws_api.hrl</code> (useful to fragment messages by
hand).
</p>
<p>
If the function returns <code>noreply</code>, nothing happens.
</p>
<p>
If the function returns <code>{close, CloseReason}</code>, the handling
process closes the connection sending a close control frame to the
client. <code>CloseReason</code> is used to set the status code and the
(optional) close reason of the close control frame. Then the handling
process terminates calling <code>Module:terminate(CloseReason,
State)</code> (if defined, see below).
</p>
</blockquote>
<p>
Because just handling messages is not enough for real applications, a basic
callback module can define optional functions, mainly to manage a callback
state. It can define one, some or all of the following functions:
</p>
<blockquote>
<strong>Module:init(Args) -> Result</strong>
<pre>
Args :: [ReqArg, InitialState]
Result :: {ok, State} | {ok, State, Timeout} | {error, Reason}
ReqArg :: #arg{}
InitialState :: term()
State :: term()
Timeout :: integer() >= 0 | infinity
Reason :: term()
</pre>
<p>
If defined, this function is called to initialize the internal state of
the callback module.
</p>
<p>
<code>ReqArg</code> is the #arg{} record supplied to
the <code>out/1</code> function and <code>InitialState</code> is the term
associated to the <code>CallbackType</code> described above.
</p>
<p>
If an integer timeout value is provided, it will overload the next
keepalive timeout (see <code>keepalive_timeout</code> option above). The
atom <code>infinity</code> can be used to wait indefinitely. If no value
is specified, the default keepalive timeout is used.
</p>
<p>
If something goes wrong during initialization, the function should
return <code>{error, Reason}</code>, where <code>Reason</code> is any
term.
</p>
</blockquote>
<blockquote>
<strong>Module:handle_open(WSState, State) -> Result</strong>
<pre>
WSState :: #ws_state{}
State :: term()
Result :: {ok, NewState} {error, Reason}
NewState :: term()
Reason :: term()
</pre>
<p>
If defined, this function is called when the connection is upgraded from
HTTP to WebSocket.
</p>
<p>
<code>WSState</code> is the state of the WebSocket connection. It can be
used to send messages to the client
using <code>yaws_api:websocket_send(WSState, Message)</code>.
</p>
<p>
<code>State</code> is the internal state of the callback module.
</p>
<p>
If the function returns <code>{ok, NewState}</code>, the handling process
will continue executing with the possibly updated internal
state <code>NewState</code>.
</p>
<p>
If the function returns <code>{error, Reason}</code>, the handling
process closes the connection and terminates
calling <code>Module:terminate({error, Reason}, State)</code> (if defined,
see below).
</p>
</blockquote>
<blockquote>
<strong>Module:handle_message(Message, State) -> Result</strong>
<pre>
Message :: see Module:handle_message/1
State :: term()
Result :: {noreply, NewState} | {noreply, NewState, Timeout} |
{reply, Reply} | {reply, Reply, NewState} |
{reply, Reply, NewState, Timeout} |
{close, CloseReason, NewState} |
{close, CloseReason, Reply, NewState}
NewState :: term()
Timeout :: integer() >= 0 | infinity
Reply :: see Module:handle_message/1
CloseReason :: see Module:handle_message/1
</pre>
<p>
If defined, this function is called in place
of <code>Module:handle_message/1</code>. The main difference with the
previous version is that this one handles the internal state of the
callback module.
</p>
<p>
<code>State</code> is internal state of the callback module.
</p>
<p>
See <code>Module:handle_message/1</code> for a description of the other
arguments and possible return values.
</p>
</blockquote>
<blockquote>
<strong>Module:handle_info(Info, State) -> Result</strong>
<pre>
Info :: timeout | term()
State :: term()
Result :: {noreply, NewState} | {noreply, NewState, Timeout} |
{reply, Reply} | {reply, Reply, NewState} |
{reply, Reply, NewState, Timeout} |
{close, CloseReason, NewState} |
{close, CloseReason, Reply, NewState}
NewState :: term()
Timeout :: integer() >= 0 | infinity
Reply :: see Module:handle_message/1
CloseReason :: see Module:handle_message/1
</pre>
<p>
If defined, this function is called when a timeout occurs
(see <code>drop_on_timeout</code> option above) or when the handling
process receives any unknown message.
</p>
<p>
<code>Info</code> is either the atom <code>timeout</code>, if a timeout
has occurred, or the received message.
</p>
<p>
See <code>Module:handle_message/1</code> for a description of the other
arguments and possible return values.
</p>
</blockquote>
<blockquote>
<strong>Module:terminate(Reason, State) -> ok</strong>
<pre>
Reason :: Status | {Status, Text} | {error, Error}
State :: term()
Status :: integer() %% RFC 6455 status code
Text :: binary()
Error :: term()
</pre>
<p>
If defined, this function is called when the handling process is about to
terminate. it should be the opposite of <code>Module:init/1</code> and do
any necessary cleaning up.
</p>
<p>
<code>Reason</code> is a term denoting the stop reason
and <code>State</code> is the internal state of the callback module.
</p>
</blockquote>
<h4>Advanced callback modules</h4>
<p>
Advanced callback modules should be used when automatic messages
defragmentation done by Yaws is not desirable or acceptable. One could be
used for example to handle data streaming over WebSockets. So, such modules
should be prepared to handle frames directly (fragmented or not).
</p>
<p>
Unlike basic callback modules, Advanced ones <strong>MUST</strong> manage an
internal state. So it <strong>MUST</strong> define the stateful
function <code>Module:handle_message/2</code> :
</p>
<blockquote>
<strong>Module:handle_message(Frame, State) -> Result</strong>
<pre>
Frame :: #ws_frame_info{} | {fail_connection, Status, Reason}
State :: term()
Result :: {noreply, NewState} | {noreply, NewState, Timeout} |
{reply, Reply} | {reply, Reply, NewState} |
{reply, Reply, NewState, Timeout} |
{close, CloseReason, NewState} |
{close, CloseReason, Reply, NewState}
Status :: integer() %% RFC 6455 status code
Reason :: binary()
NewState :: term()
Timeout :: integer() >= 0 | infinity
Reply :: see Module:handle_message/1
CloseReason :: see Module:handle_message/1
</pre>
<p>
This function is called when a frame is
received. The <code>#ws_frame_info{}</code> record, defined
in <code>include/yaws_api.hrl</code>, provides all details about this
frame. <code>State</code> it the internal state of the callback module.
</p>
<p>
If an error occurs during the frame parsing, the
term <code>{fail_connection, Status, Reason}</code> is passed,
where <code>Status</code> is the numerical status code corresponding to
the error
(see <a href="http://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455 -
Section 7.4.1</a>) and <code>Reason</code> the binary containing optional
information about it.
</p>
<p>
This function returns the same values as specified for the basic callback
module's <code>Module:handle_message/2</code>. See above for details.
</p>
</blockquote>
<p>
Advanced callback modules can also define the same optional functions as
basic callback modules (except <code>Module:handle_messages/2</code> which
is mandatory here, of course). See above for details.
</p>
<h3>Record definitions</h3>
<p>
Here is the definition of records used in callback modules, defined
in <code>include/yaws_api.hrl</code>:
</p>
<div class="box">
<pre>
%% Corresponds to the frame sections as in
%% http://tools.ietf.org/html/rfc6455#section-5.2
%% plus 'data' and 'ws_state'. Used for incoming frames.
-record(ws_frame_info, {
fin,
rsv,
opcode,
masked,
masking_key,
length,
payload,
data, % The unmasked payload. Makes payload redundant.
ws_state % The ws_state after unframing this frame.
% This is useful for the endpoint to know what type of
% fragment a potentially fragmented message is.
}).
%% Used for outgoing frames. No checks are done on the validity of a frame. This
%% is the application's responsability to send valid frames.
-record(ws_frame, {
fin = true,
rsv = 0,
opcode,
payload = &lt;&lt;&gt;&gt;
}).
%%----------------------------------------------------------------------
%% The state of a WebSocket connection.
%% This is held by the ws owner process and passed in calls to yaws_api.
%%----------------------------------------------------------------------
-type frag_type() :: text
| binary
| none. % The WebSocket is not expecting continuation
% of any fragmented message.
-record(ws_state, {
vsn :: integer(), % WebSocket version number
sock, % gen_tcp or gen_ssl socket
frag_type :: frag_type()
}).
</pre>
</div>
<erl>
out(A) ->
{ssi, "END2",[],[]}.
</erl>
Jump to Line
Something went wrong with that request. Please try again.