Websockets
and
Elm ports

Http request/response

POST /login/
HTTP/1.1 200 OK
You are logged in!
POST /login/
HTTP/1.1 200 OK
You are logged in!
"Hey the fox is logged in"
"Hey the fox is logged in"
POST /login/
HTTP/1.1 200 OK
You are logged in!
"Hey the fox is logged in"
"Hey the fox is logged in"

How to?

GET /userlist/

Short polling

GET /userlist/
GET /userlist/

Client periodically issues request to the server

(e.g. every second)

Pros

  • Simple to setup
  • No special server nor client support

Cons

  • Very inefficient
  • Unusable for real time games

Short polling

GET /next-connected/

Long polling

GET /next-connected/

The server does not respond immediately, it keeps the connection open.

Long polling

GET /next-connected/
POST /login/

Long polling

GET /next-connected/
POST /login/
"Opera is logged in!"

Long polling

GET /next-connected/
GET /next-connected/

The client has received a response, it opens a new connection

Long polling

GET /next-connected/
GET /next-connected/
POST /login/

Long polling

GET /next-connected/
GET /next-connected/
POST /login/
"Chrome is logged in!"

Long polling

Pros

  • No special client support
  • Usable for "light" real time game or instant messaging

Cons

  • Special support needed for server
  • Huge overhead for each message (the headers size can exceed the payload size)

Long polling

WebSockets

GET /app/socket HTTP/1.1
Upgrade: websocket
Connection: Upgrade
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

WS (or WSS) channel

WebSockets

WebSockets

User connection

POST /login/

WebSockets

User connection

"Opera is logged in"

"Opera is logged in"

"Opera is logged in"

WebSockets

User connection

WebSockets

Real time game

"I've hit the left arrow"

WebSockets

Real time game

"I've hit the left arrow"

Chrome's position is (10, 50)

Chrome's position is (10, 50)

WebSockets

Real time game

Chrome's position is (10, 50)

WebSockets

Instant messaging

WebSockets

Instant messaging

"Hi Firefox!"

WebSockets

Instant messaging

"Hi Firefox!"

"Chrome said: 'Hi Firefox!' "

Pros

  • Very low overhead for each message
  • Bi directional

Cons

  • Need special support for server
  • Not supported by (very) old browsers

WebSockets

WS protocol is low level

we will use the lib SocketIO

https://socket.io/

WS protocol is low level

we will use the lib SocketIO

https://socket.io/

pip install flask-socketio
from flask import Flask
import flask_socketio

app = Flask(__name__)
io = flask_socketio.SocketIO(app)

@app.route("/")
@flask_login.login_required
def home():
  return render_template('index.html')

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )
from flask import Flask
import flask_socketio

app = Flask(__name__)
io = flask_socketio.SocketIO(app)

@app.route("/")
@flask_login.login_required
def home():
  return render_template('index.html')

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )

Http request

from flask import Flask
import flask_socketio

app = Flask(__name__)
io = flask_socketio.SocketIO(app)

@app.route("/")
@flask_login.login_required
def home():
  return render_template('index.html')

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )

WS channel,
on connection

from flask import Flask
import flask_socketio

app = Flask(__name__)
io = flask_socketio.SocketIO(app)

@app.route("/")
@flask_login.login_required
def home():
  return render_template('index.html')

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )

channel we are emitting on

from flask import Flask
import flask_socketio

app = Flask(__name__)
io = flask_socketio.SocketIO(app)

@app.route("/")
@flask_login.login_required
def home():
  return render_template('index.html')

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )

channel we are emitting on

Data we are sending (translated to JSON)

from flask import Flask
import flask_socketio

app = Flask(__name__)
io = flask_socketio.SocketIO(app)

@app.route("/")
@flask_login.login_required
def home():
  return render_template('index.html')

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )

channel we are emitting on

Data we are sending (translated to JSON)

Send to all users

"Opera is logged in"

"Opera is logged in"

"Opera is logged in"

WebSockets

User connection

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('userlist', function(users){
  set_userlist(users);
});

</script>

server.py

index.html

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('userlist', function(users){
  set_userlist(users);
});

</script>

server.py

index.html

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('userlist', function(users){
  set_userlist(users);
});

</script>

server.py

index.html

@io.on('connect')
def ws_connect():
    io.emit('userlist', [
            "Arthur",
            "Zaphod",
        ],
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('userlist', function(users){
  set_userlist(users);
});

</script>

server.py

index.html

"Opera is logged in"

"Opera is logged in"

"Opera is logged in"

WebSockets

User connection

"I've hit the left arrow"

Chrome's position is (10, 50)

Chrome's position is (10, 50)

WebSockets

Real time game

Chrome's position is (10, 50)

@io.on('move')
def handle_move(movement):
    current_user.go_to(movement)
    io.emit('new_position', {
          "user": current_user.id,
          "position": current_user.position,
        },
        broadcast=True
    )

server.py

@io.on('move')
def handle_move(movement):
    current_user.go_to(movement)
    io.emit('new_position', {
          "user": current_user.id,
          "position": current_user.position,
        },
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('new_position', function(data){
  players[data.user].position = data.position
});

<on new move>(function(movement) {
    socket.emit('move', movement);
});

server.py

index.html

@io.on('move')
def handle_move(movement):
    current_user.go_to(movement)
    io.emit('new_position', {
          "user": current_user.id,
          "position": current_user.position,
        },
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('new_position', function(data){
  players[data.user].position = data.position
});

<on new move>(function(movement) {
    socket.emit('move', movement);
});

server.py

index.html

Fake syntaxe, more on that later

@io.on('move')
def handle_move(movement):
    current_user.go_to(movement)
    io.emit('new_position', {
          "user": current_user.id,
          "position": current_user.position,
        },
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('new_position', function(data){
  players[data.user].position = data.position
});

<on new move>(function(movement) {
    socket.emit('move', movement);
});

server.py

index.html

@io.on('move')
def handle_move(movement):
    current_user.go_to(movement)
    io.emit('new_position', {
          "user": current_user.id,
          "position": current_user.position,
        },
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('new_position', function(data){
  players[data.user].position = data.position
});

<on new move>(function(movement) {
    socket.emit('move', movement);
});

server.py

index.html

@io.on('move')
def handle_move(movement):
    current_user.go_to(movement)
    io.emit('new_position', {
          "user": current_user.id,
          "position": current_user.position,
        },
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('new_position', function(data){
  players[data.user].position = data.position
});

<on new move>(function(movement) {
    socket.emit('move', movement);
});

server.py

index.html

@io.on('move')
def handle_move(movement):
    current_user.go_to(movement)
    io.emit('new_position', {
          "user": current_user.id,
          "position": current_user.position,
        },
        broadcast=True
    )
<script type="text/javascript" charset="utf-8">
var socket = io();

socket.on('new_position', function(data){
  players[data.user].position = data.position
});

<on new move>(function(movement) {
    socket.emit('move', movement);
});

server.py

index.html

"I've hit the left arrow"

Chrome's position is (10, 50)

Chrome's position is (10, 50)

WebSockets

Real time game

Chrome's position is (10, 50)

Elm does not handle Websockets natively

But Elm has "ports"

WebSocket

Ports

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});

index.html

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});
port move: String -> Cmd msg
-- ...
-- in update:
    LeftArrowPressed ->
        ( model, move "Left")

    RightArrowPressed ->
        ( model, move "Right")

index.html

Main.elm

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});
port move: String -> Cmd msg
-- ...
-- in update:
    LeftArrowPressed ->
        ( model, move "Left")

    RightArrowPressed ->
        ( model, move "Right")

index.html

Main.elm

Elm to JS port

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});
port move: String -> Cmd msg
-- ...
-- in update:
    LeftArrowPressed ->
        ( model, move "Left")

    RightArrowPressed ->
        ( model, move "Right")

index.html

Main.elm

Elm to JS port

JS code subscribes to Elm "events"

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});
port move: String -> Cmd msg
-- ...
-- in update:
    LeftArrowPressed ->
        ( model, move "Left")

    RightArrowPressed ->
        ( model, move "Right")

index.html

Main.elm

JS code subscribes to Elm "events"

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});

index.html

port newPosition: (PosData -> msg) -> Sub msg

type Msg
    = GotNewPosition PosData
    | ...

subscriptions model =
    newPosition GotNewPosition

Main.elm

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});

index.html

port newPosition: (PosData -> msg) -> Sub msg

type Msg
    = GotNewPosition PosData
    | ...

subscriptions model =
    newPosition GotNewPosition

Main.elm

JS to Elm port

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});

index.html

port newPosition: (PosData -> msg) -> Sub msg

type Msg
    = GotNewPosition PosData
    | ...

subscriptions model =
    newPosition GotNewPosition

Main.elm

JS to Elm port

Elm code subscribes to JS events

<script type="text/javascript" charset="utf-8">
var socket = io();
var app = Elm.Main.init({ node: foo});

socket.on('new_position', function(data){
  app.ports.newPosition.send(data);
});

app.ports.move.subscribe(function(movement) {
    socket.emit('move', movement);
});

index.html

port newPosition: (PosData -> msg) -> Sub msg

type Msg
    = GotNewPosition PosData
    | ...

subscriptions model =
    newPosition GotNewPosition

Main.elm

Elm code subscribes to JS events

port newPosition: (PosData -> msg) -> Sub msg

type Msg
    = GotNewPosition PosData
    | ...

subscriptions model =
    newPosition GotNewPosition

type alias PosData =
    { user: String, position: (Int, Int) }

-- in update:
    GotNewPosition posData ->
        ( setNewPos posData model, Cmd.none)

setNewPos : PosData -> Model -> Model
setNewPos posData model =
    ...

Websockets - TWTS 06

By sebbes

Websockets - TWTS 06

  • 782