Slide 1

Slide 1 text

It doesn’t have to be REST WebSockets in Go GOPHERFEST EDITION

Slide 2

Slide 2 text

REST GET DELETE POST PUT Resources ?

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Client-Server

Slide 5

Slide 5 text

Client-Server Stateless

Slide 6

Slide 6 text

Client-Server Stateless Cache

Slide 7

Slide 7 text

Client-Server Stateless Cache Uniform Interface

Slide 8

Slide 8 text

Client-Server Stateless Cache Uniform Interface Layered System

Slide 9

Slide 9 text

Client-Server Stateless Cache Uniform Interface Layered System Code on Demand

Slide 10

Slide 10 text

HTTP 0.9 HTTP 1.0 HTTP 1.1 HTTP/2 GraphQL WebSocket 1991 1996 1999 2000 2005 2008 2013 2015 2016 2018 REST React High React Adoption Towards Real-Time Rendering & Updating AJAX Expected Real-Time Singularity

Slide 11

Slide 11 text

WebSockets

Slide 12

Slide 12 text

Chats Games

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Server ws://… Client Messages Text/Binary

Slide 17

Slide 17 text

Server ws://… Client Messages Text/Binary - Message-oriented protocol - Messages are split into frames - Payload can be binary or text - Supports fragmentation

Slide 18

Slide 18 text

Server ws://… Client Messages Text/Binary - Message-oriented protocol - Messages are split into frames - Payload can be binary or text - Supports fragmentation

Slide 19

Slide 19 text

https://godoc.org/?q=websocket

Slide 20

Slide 20 text

github.com/gorilla/websocket 3958 stars, 2393 imports golang.org/x/net/websocket 1412 imports

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

package main import ( "log" "net/http" ) func handleUpgrade(w http.ResponseWriter, r *http.Request) { // TODO } func main() { http.HandleFunc("/websocket", handleUpgrade) err := http.ListenAndServe(":4000", nil) if err != nil { panic(err) } }

Slide 23

Slide 23 text

package main import ( "log" "net/http" ) var upgrader = websocket.Upgrader{} func handleUpgrade(w http.ResponseWriter, r *http.Request) { // TODO } func main() { http.HandleFunc("/websocket", handleUpgrade) err := http.ListenAndServe(":4000", nil) if err != nil { panic(err) } }

Slide 24

Slide 24 text

package main import ( "log" "net/http" ) var upgrader = websocket.Upgrader{} func handleUpgrade(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { panic(err) } defer conn.Close() // TODO } func main() { http.HandleFunc("/websocket", handleUpgrade) // …

Slide 25

Slide 25 text

package main import ( "log" "net/http" ) var upgrader = websocket.Upgrader{} func handleUpgrade(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { panic(err) } defer conn.Close() handleConnection(conn) } func main() { http.HandleFunc("/websocket", handleUpgrade) // …

Slide 26

Slide 26 text

func handleConnection(conn *websocket.Conn) { for { messageType, message, err := c.ReadMessage() if err != nil { break } log.Println(message) err = c.WriteMessage(messageType, message) if err != nil { break } } }

Slide 27

Slide 27 text

This is a solved problem!

Slide 28

Slide 28 text

Parse, Firebase, Pusher - Data synchronization services offer an off-the-shelf software solution - Trade the cost of implementation against the cost of learning their API - Force an opinionated structure on your data - Limited querying capabilities - They manage sessions for you but you still need to integrate them - Upside: offers scalability out of the box

Slide 29

Slide 29 text

Backend Clients API Database HTTP

Slide 30

Slide 30 text

Backend Clients API Database HTTP Polling

Slide 31

Slide 31 text

Push Backend Clients API Database WebSocket Bus

Slide 32

Slide 32 text

Backend Clients API Database WebSocket Bus Subscribe Publish State change Push

Slide 33

Slide 33 text

Backend Clients API Database WebSocket Bus Subscribe Publish State change Push

Slide 34

Slide 34 text

Backend Clients API Database WebSocket {"cmd”:"listUsers"} Push

Slide 35

Slide 35 text

Backend Clients API Database WebSocket "CONNECT" "LIST_USERS" Push

Slide 36

Slide 36 text

type MessageEndpoint func(ctx context.Context, m Message, w *Writer) error type Router struct { endpoints map[string]MessageEndpoint writer map[string]*Writer } func (r *Router) HandleConnection(conn *websocket.Conn) { w := r.NewWriter(conn) for { _, message, err := conn.ReadMessage() if err != nil { break } request := string(message) r.writer[w.ID] = w r.Route(context.Background(), w, request) } delete(r.writer, r.ID(conn)) }

Slide 37

Slide 37 text

type MessageEndpoint func(ctx context.Context, m Message, w *Writer) error type Router struct { endpoints map[string]MessageEndpoint writer map[string]*Writer } func (r *Router) HandleConnection(conn *websocket.Conn) { w := r.NewWriter(conn) for { _, message, err := conn.ReadMessage() if err != nil { break } request := string(message) r.writer[w.ID] = w go r.Route(context.Background(), w, request) } delete(r.writer, r.ID(conn)) }

Slide 38

Slide 38 text

type Writer struct { ID int conn *websocket.Conn } func (w *Writer) Write(data []byte) (int, error) { err := w.conn.WriteMessage(websocket.TextMessage, data) if err != nil { return 0, err } return len(data), nil }

Slide 39

Slide 39 text

type Writer struct { ID int conn *websocket.Conn } func (w *Writer) Write(data []byte) (int, error) { err := w.conn.WriteMessage(websocket.TextMessage, data) if err != nil { return 0, err } return len(data), nil } panic: concurrent write to websocket connection

Slide 40

Slide 40 text

type Writer struct { ID int conn *websocket.Conn } func (w *Writer) Write(data []byte) (int, error) { err := w.conn.WriteMessage(websocket.TextMessage, data) if err != nil { return 0, err } return len(data), nil }

Slide 41

Slide 41 text

type Writer struct { ID int mu sync.RWMutex conn *websocket.Conn } func (w *Writer) Write(data []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() err := w.conn.WriteMessage(websocket.TextMessage, data) if err != nil { return 0, err } return len(data), nil }

Slide 42

Slide 42 text

Is it safe to send passwords in clear text over WebSockets?

Slide 43

Slide 43 text

Security - There are two modes: unencrypted ws:// and encrypted wss:// - Encrypted mode uses TLS/SSL encryption to encrypt all data sent - This includes the initial hand-shake What about sessions? - WebSockets are stateful: we can and should make use of this.

Slide 44

Slide 44 text

Sessions - Naive approach: be stateless, send a session ID with every request - Alternative: make use of the persistent connection - Authenticate once through query parameter when connecting wss://example.com/api?sessionID=token - Protects much better against CSRF attacks

Slide 45

Slide 45 text

package main import ( "log" "net/http" ) var upgrader = websocket.Upgrader{} func handleUpgrade(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { panic(err) } defer conn.Close() handleConnection(conn) }

Slide 46

Slide 46 text

package main import ( "log" "net/http" ) var upgrader = websocket.Upgrader{} func handleUpgrade(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { panic(err) } sessionID := req.URL.Query().Get("sessionID") defer conn.Close() handleConnection(conn, sessionID) }

Slide 47

Slide 47 text

Scaling WebSocket Architectures When Client 1 persists data through Server 1 how will Client 2 learn about this who is connected to Server 2? - Publish/Subscribe Broker to implement message bus - This also takes care of handling failover - Load balancers like AWS ELB support Proxy Protocol for TCP mode - There is a hard limit of 65,535 ports per IP address - Combination of DNS and software load balancer

Slide 48

Slide 48 text

Client Server Server Server Load Balancer Load Balancer Client Client Client Client Client Client Client Server Server Message Bus DNS Server

Slide 49

Slide 49 text

https://github.com/gobwas/ws - Efficient low-level WebSocket library - Allow users to reuse I/O buffers between connections - Export efficient low-level interface for working with the protocol Zero-Copy Upgrade - Skips the need to use HTTP for upgrading - ws.Upgrade() accepts io.ReadWriter (net.Conn)

Slide 50

Slide 50 text

Summary - REST can be implemented using WebSockets - WebSockets allow us to implement a push architecture - Freedom in designing a sub-protocol tailored to your system’s needs - Also useful for connecting backend components - Gorilla WebSocket is a great implementation of RFC 6455 - Rolling your own implementation can be costly - Scaling WebSockets is nontrivial - Bad compatibility to the outside world

Slide 51

Slide 51 text

Thank you. @konradreiche

Slide 52

Slide 52 text

Questions? @konradreiche