readme.md

Dependency-Free, Compliant Websocket Server written in Elixir. 

Kitchen sink *not* included. Every mandatory [Autobahn Testsuite](https://github.com/crossbario/autobahn-testsuite) case is passing. (Three fragmented UTF-8 are flagged as non-strict and as compression is not implemented, these are all flagged as "Unimplemented")

## Usage

Include the dependency in your project:

```
{:exms, "~> 0.0.1"}
```

Add an `:exws` section to your config:
```
config :exws,
  port: 4545,
  handler: YourApp.YourWSHandler
```

Define your handler:
```elixir
defmodule YourApp.YourWSHandler do
  use ExWs.Handler

  def message(_data, state) do
    # do something with data
    state
  end
end
```

## Writing
From within your handler, you can use the `write/1` function to
send a message to the user:

```elixir
def message(data, state) do
  # data is an iolist
  write(data) # echo the message back to the user
  state
end
```

## Handshake
The `handshake/3` callback lets you handle the initial handshake:

```elixir
# this is the default implementation
def handshake(_path, _headers, state) do
  {:ok, state}
end
```

Where `path` is the requested URL path as a string, and `headers` is a map with lowercase string keys.

If you want to reject the handshake, say because the path/headers does not contain the correct authentication, return a `{:close, ExWs.invalid_handshake/1}`:

```elixir
def handshake(_path, headers, state) do
  case lookup_one_time_token(headers["token"]) do
    {:ok, user_id} -> {:ok, %{user_id: user_id}} # set a new state
    _ -> {:close, ExWs.invalid_handshake("invalid_token")}
  end
end
```

Note that the value given to `ExWs.invalid_handshale/1` (in the above case, we're talking about "invalid_token") is placed in the `Error` header of the handshake response (for troubleshooting purpose)


### init
You can set the initial state by providing an `init/0` callback:

```elixir
# this is the default implementation
def init(), do: nil
````

### Closed
The `closed/2` callback is called whenever the socket is closed:

```elixir
# default closed/2 implementation
defp closed(_reason, state) do
  shutdown()
  state
end
```

If you overwrite `closed/2`, you almost certainly want to call `shutdown/0` (it both closes the socket and shuts down the underlying GenServer).

## Handler Functions
Within your handler, the following functions are available:

- `ping/0` send a ping message to the client
- `write/1` writes the message to the client
- `close/0` close the connection
- `get_socket/0` gets the underlying socket

Note that `get_socket/0` will return the socket during `init/0` and `closed/2` (but the socket can be closed by the other side at any point).

Note that if you call `close/0` directly, the `closed/2` callback will be executed.

## Write Optimizations
All WebSocket messages are framed and there's some overhead in creating this framing. When you call `write/1` with a binary value, the handler will frame your payload and write the framed message to the socket.

For static messages, you can opt to pre-frame the message using the `ExWs.bin/1` and `ExWs.txt/1` functions. `write/1` will detect these pre-framed messages and send them directly as-is.

```elixir
defmodule YourApp.YourWSHandler do
  use ExWs.Handler

  @message_over_9000 ExWs.txt(" 9000!!")
  def message("it's over", state) do
    write(@message_over_9000)
    state
  end
end
```

## txt vs bin
WebSocket has a separate message type for binary data and text data. Implementations must reject any message declared as txt which is not valid UTF8. 

This library does not do this validation. The `message/2` callback receives both text and binary messages.

If you want to differentiate between the two, implement `message/3` instead of `message/2`:

```elixir
def message(op, data, state) do
  # op will be :txt or :bin
end
```

The default `write/1` function uses the text type. You can override `write/1` to change this behavior:

```elixir
defp write(data) do
  ExWs.write(get_socket(), ExWs.bin(data))
end
```

Or you can do it on a case-by-case basis:
```elixir
write(ExWs.bin(some_data))

write(data) 
# as as
write(ExBin.txt(data))
```

## Direct Socket Usage
For performance reason, you may want to write directly to the socket, without going through the handler. For example, you might implement room/channel logic by storing the socket directly into the ETS table (writing to sockets from concurrent elixir processes is fine).

As we already saw, the `get_socket/0` helper will return the socket. But you cannot write to the socket directly using `gen_tcp.send/2` since weboscket messages must be framed.

You have two options, either use the `ExWs.bin/1` and `ExWs.txt/1` helpers to frame data:

```elixir
:gen_tcp.send(socket, ExWs.txt("leto atreides"))
```

Or use the `ExWs.write/2` helper:

```elixir
ExWs.write(socket, "leto atreides")
```

Note that `ExWs` also exposes `ping/1` and `close/1`.