<erl> out(A) -> {ssi, "TAB.inc", "%%",[{"websockets", "choosen"}]}. </erl>

# WebSockets in Yaws

WebSockets! The new kid in town! Joe loves it, maybe you should too?

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?

### A simple example

First of all, here is a simple example. It shows how to upgrade connections from HTTP to WebSocket.

    <erl>
    out(A) ->
        {ssi, "websockets_example_endpoint.yaws",[],[]}.
    </erl>

The above code can be executed Here.

### Establish a WebSocket connection

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:

    {websocket, CallbackMod, Options}

where `CallbackMod` is an atom identifying the WebSocket callback module, and `Options` is a (possibly empty) list (see below for details).

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).

#### Supported Options

The following options are available:

- `{callback, CallbackType}`

  Specify the type of the callback module. `CallbackType` can be either of the following:

  - `basic` - Same as `{basic, []}`. This is the default.
  - `{basic, InitialState}` - Indicate your callback module is a basic callback module. `InitialState` is the callback's initial state for handling this client.
  - `{advanced, InitialState}` - Same as above but for an advanced callback module.
- `{origin, Origin}`

  Specify the `Origin` URL from which messages will be accepted. This is useful for protecting against cross-site attack. The option defaults to `any`, meaning calls will be accepted from any origin.

- `{keepalive, KeepAliveBoolean}`

  If true, Yaws will automatically send a ping message every `keepAliveTimeout` milliseconds. By default keepalive pings are disabled.

- `{keepalive_timeout, keepAliveTimeout}`

  Specify the interval in milliseconds to send keepalive pings, by default 30000. Ignored if `KeepAliveBoolean` is false.

- `{keepalive_grace_period, KeepAliveGracePeriod}`

  Specify the amount of time, in milliseconds, to wait after sending a keepalive ping. If no message is received within `KeepAliveGracePeriod` milliseconds, a timeout will occur. Depending on the `DropBoolean` value, a close frame is sent with the status code 1006 (if `DropBoolean` is true) or the callback module is notified (see `Module:handle_info/2` below).
  By default, `KeepAliveGracePeriod` is set to 2000. Ignored if `KeepAliveBoolean` is false.

- `{drop_on_timeout, DropBoolean}`

  If true, a close frame is sent with the status code 1006 when a timeout occurs after a keepalive ping has been sent (see `KeepAliveGracePeriod`). Disabled by default. Ignored if `KeepAliveBoolean` is false.

- `{close_timeout, CloseTimeout}`

  After sending a close frame to a client, Yaws will wait for the client acknowledgement for `CloseTimeout` milliseconds. Then it will close the underlying TCP connection. By default `CloseTimeout` is set to 5000.

- `{close_if_unmasked, CloseUnmaskedBoolean}`

  If true, Yaws will reject any unmasked incoming frame by sending a close frame with the status code 1002. Disabled by default.
  Note: According to RFC 6455, a client must mask all frames that it sends to the server (See RFC 6455 - Section 5.1).

- `{max_frame_size, MaxFrameSize}`

  Specify the maximum allowed size, in bytes, for received frames. By default 16MB. It is also the maximum size for unfragmented messages.
  This limit is checked for all types of callback module.

- `{max_message_size, MaxMsgSize}`

  Specify the maximum allowed message size in bytes, by default 16MB.
  This limit is checked only for basic callback modules.

- `{auto_fragment_message, AutoFragBoolean}`

  If true, outgoing messages will be automatically fragmented if their payload exceeds `OutFragSize` bytes. This flag is set to false by default.

- `{auto_fragment_threshold, OutFragSize}`

  Specify the maximum payload size of each fragment if `AutoFragBoolean` is true. `OutFragSize` is set to 1MB by default. Ignored is `AutoFragBoolean` is false.

### WebSocket callback modules

All frames received on a WebSocket connection are passed to the callback modules specified during the connection establishment by calling `Module:handle_message/1` or `Module:handle_message/2`, depending on whether it’s a basic or an advanced callback module.

#### Basic callback modules

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.

A basic callback module MUST define the stateless function `Module:handle_message/1`:

> Module:handle_message(Message) -> Result

>         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()

> This function is called when a message is received. `{text, Data}` (or `{binary, Data}`) is the unfragmented text (or binary) message. When the client closes the connection, the callback module is notified with the message `{close, Status, Reason}`, where `Status` is the numerical status code sent by the client or the value 1000 (see RFC 6455 - Section 7.4.1) if the client sent no status code. For an abnormal client closure, the status code is 1006 (as specified by RFC 6455 - Section 7.1.5). `Reason` is a binary containing any text the client sent to indicate the reason for closing the socket; this binary may be empty.

> If the function returns `{reply, Reply}`, `Reply` is sent to the client. It is possible to send one or more unfragmentated messages by returning `{Type, Data}` or `[{Type, Data}]`. It is also possible to send one or more frames using the `#ws_frame{}` record instead, defined in `include/yaws_api.hrl` (useful to fragment messages by hand).

> If the function returns `noreply`, nothing happens.

> If the function returns `{close, CloseReason}`, the handling process closes the connection sending a close control frame to the client. `CloseReason` is used to set the status code and the (optional) close reason of the close control frame. Then the handling process terminates calling `Module:terminate(CloseReason, State)` (if defined, see below).

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:

> Module:init(Args) -> Result

>         Args   :: [ReqArg, InitialState]
>         Result :: {ok, State} | {ok, State, Timeout} | {error, Reason}
>           ReqArg       :: #arg{}
>           InitialState :: term()
>           State        :: term()
>           Timeout      :: integer() >= 0 | infinity
>           Reason       :: term()

> If defined, this function is called to initialize the internal state of the callback module.

> `ReqArg` is the #arg{} record supplied to the `out/1` function and `InitialState` is the term associated to the `CallbackType` described above.

> If an integer timeout value is provided, it will overload the next keepalive timeout (see `keepalive_timeout` option above). The atom `infinity` can be used to wait indefinitely. If no value is specified, the default keepalive timeout is used.

> If something goes wrong during initialization, the function should return `{error, Reason}`, where `Reason` is any term.

> Module:handle_open(WSState, State) -> Result

>         WSState :: #ws_state{}
>         State   :: term()
>         Result  :: {ok, NewState} {error, Reason}
>           NewState :: term()
>           Reason   :: term()

> If defined, this function is called when the connection is upgraded from HTTP to WebSocket.

> `WSState` is the state of the WebSocket connection. It can be used to send messages to the client using `yaws_api:websocket_send(WSState, Message)`.

> `State` is the internal state of the callback module.

> If the function returns `{ok, NewState}`, the handling process will continue executing with the possibly updated internal state `NewState`.

> If the function returns `{error, Reason}`, the handling process closes the connection and terminates calling `Module:terminate({error, Reason}, State)` (if defined, see below).

> Module:handle_message(Message, State) -> Result

>         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

> If defined, this function is called in place of `Module:handle_message/1`. The main difference with the previous version is that this one handles the internal state of the callback module.

> `State` is internal state of the callback module.

> See `Module:handle_message/1` for a description of the other arguments and possible return values.

> Module:handle_info(Info, State) -> Result

>         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

> If defined, this function is called when a timeout occurs (see `drop_on_timeout` option above) or when the handling process receives any unknown message.

> `Info` is either the atom `timeout`, if a timeout has occurred, or the received message.

> See `Module:handle_message/1` for a description of the other arguments and possible return values.

> Module:terminate(Reason, State) -> ok

>         Reason :: Status | {Status, Text} | {error, Error}
>         State  :: term()
>           Status :: integer() %% RFC 6455 status code
>           Text   :: binary()
>           Error  :: term()

> If defined, this function is called when the handling process is about to terminate. it should be the opposite of `Module:init/1` and do any necessary cleaning up.

> `Reason` is a term denoting the stop reason and `State` is the internal state of the callback module.

#### Advanced callback modules

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).

Unlike basic callback modules, Advanced ones MUST manage an internal state. So it MUST define the stateful function `Module:handle_message/2` :

> Module:handle_message(Frame, State) -> Result

>         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

> This function is called when a frame is received. The `#ws_frame_info{}` record, defined in `include/yaws_api.hrl`, provides all details about this frame. `State` it the internal state of the callback module.

> If an error occurs during the frame parsing, the term `{fail_connection, Status, Reason}` is passed, where `Status` is the numerical status code corresponding to the error (see RFC 6455 - Section 7.4.1) and `Reason` the binary containing optional information about it.

> This function returns the same values as specified for the basic callback module's `Module:handle_message/2`. See above for details.

Advanced callback modules can also define the same optional functions as basic callback modules (except `Module:handle_messages/2` which is mandatory here, of course). See above for details.

### Record definitions

Here is the definition of records used in callback modules, defined in `include/yaws_api.hrl`:

    %% Corresponds to the frame sections as in
    %% https://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 = <<>>
             }).

    %%----------------------------------------------------------------------
    %% 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()
             }).

<erl> out(A) -> {ssi, "END2",[],[]}. </erl>