Upgrade to Pro — share decks privately, control downloads, hide ads and more …

It doesn't have to be REST: WebSockets in Go

It doesn't have to be REST: WebSockets in Go

The speed of Go should make us reconsider well established paradigms. When developing APIs it is practically impossible not to mention REST yet many applications would benefit from real-time interactivity. There are different WebSocket implementation in the Go ecosystem. This talk will give you headspace to consider WebSockets as an alternative communication protocol for your application by giving an overview of the current state of the art of WebSockets in Go.

Konrad Reiche

August 18, 2017
Tweet

More Decks by Konrad Reiche

Other Decks in Technology

Transcript

  1. It doesn’t have
    to be REST
    WebSockets in Go

    View full-size slide

  2. @konradreiche

    View full-size slide

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

    View full-size slide

  4. REST
    GET
    DELETE
    POST
    PUT
    Resources
    ?

    View full-size slide

  5. REST (Representational State Transfer)
    - Defined by Roy T. Fielding in his dissertation:
    “Architectural Styles and the Design of Network-based Software Architectures”
    - Fielding: there are two perspectives on the process of architectural design
    - Build the architecture from familiar components until needs are satisfied
    - Start with system needs; incrementally add constraints when needed
    - First one emphasizes creativity and unbound vision
    - Second one emphasizes restraint and understanding the system context

    View full-size slide

  6. Client-Server

    View full-size slide

  7. Client-Server
    Stateless

    View full-size slide

  8. Client-Server
    Stateless
    Cache

    View full-size slide

  9. Client-Server
    Stateless
    Cache
    Uniform Interface

    View full-size slide

  10. Client-Server
    Stateless
    Cache
    Uniform Interface
    Layered System

    View full-size slide

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

    View full-size slide

  12. GET ws://localhost:4000/ HTTP/1.1
    Origin: http://localhost:4000
    Host: localhost:4000
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Key: zy6Dy9mSAIM7GJZNf9rI1A==
    Sec-WebSocket-Version: 13
    Open Handshake
    Request

    View full-size slide

  13. HTTP/1.1 101 Switching Protocols
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Accept: EDJa7WCAQQzMCYNJM42Syuo9SqQ=
    Open Handshake
    Response

    View full-size slide

  14. Server
    ws://…
    Client
    Messages
    Text/Binary

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  17. package main
    import (
    "net/http"
    )
    func main() {
    err := http.ListenAndServe(":4000", nil)
    if err != nil {
    panic(err)
    }
    }

    View full-size slide

  18. package main
    import (
    "net/http"
    "golang.org/x/net/websocket"
    )
    func main() {
    http.Handle("/websocket", websocket.Handler(nil))
    err := http.ListenAndServe(":4000", nil)
    if err != nil {
    panic(err)
    }
    }

    View full-size slide

  19. package main
    import (
    "net/http"
    "golang.org/x/net/websocket"
    )
    func Handle(ws *websocket.Conn) {
    // TODO
    }
    func main() {
    http.Handle("/websocket", websocket.Handler(Handle))
    err := http.ListenAndServe(":4000", nil)
    if err != nil {
    panic(err)
    }
    }

    View full-size slide

  20. package main
    import (
    "net/http"
    "golang.org/x/net/websocket"
    )
    func Handle(ws *websocket.Conn) {
    // TODO
    }
    func main() {
    http.Handle("/websocket", websocket.Handler(Handle))
    err := http.ListenAndServe(":4000", nil)
    if err != nil {
    panic(err)
    }
    }
    io.Reader
    io.Writer

    View full-size slide

  21. package main
    import (
    "net/http"
    "golang.org/x/net/websocket"
    )
    func Handle(ws *websocket.Conn) {
    ws.Write([]byte("I am alive"))
    }
    func main() {
    http.Handle("/websocket", websocket.Handler(Handle))
    err := http.ListenAndServe(":4000", nil)
    if err != nil {
    panic(err)
    }
    }

    View full-size slide

  22. package main
    import (
    "net/http"
    "golang.org/x/net/websocket"
    )
    func main() {
    origin := "http://localhost/"
    _, err := websocket.Dial("ws://localhost:4000/websocket", "", origin)
    if err != nil {
    panic(err)
    }
    }

    View full-size slide

  23. package main
    import (
    "net/http"
    "golang.org/x/net/websocket"
    )
    func main() {
    origin := "http://localhost/"
    ws, err := websocket.Dial("ws://localhost:4000/websocket", "", origin)
    if err != nil {
    panic(err)
    }
    _, err := ws.Write([]byte("Hello? Anybody here?"))
    if err != nil {
    panic(err)
    }
    }

    View full-size slide

  24. package main
    import (
    "net/http"
    "golang.org/x/net/websocket"
    )
    func main() {
    origin := "http://localhost/"
    ws, err := websocket.Dial("ws://localhost:4000/websocket", "", origin)
    if err != nil {
    panic(err)
    }
    _, err := ws.Write([]byte("Hello? Anybody here?"))
    if err != nil {
    panic(err)
    }
    msg := make([]byte, 512)
    _, err = ws.Read(msg)
    // …

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  27. github.com/gorilla/websocket golang.org/x/net
    RFC 6455 Features
    Passes Autobahn Test Suite Yes No
    Receive fragmented message Yes No
    Send close message Yes No
    Send pings and receive pongs Yes No
    Get the type of a received data message Yes Yes
    Other Features
    Read message using io.Reader Yes No?
    Write message using io.WriterCloser Yes No?

    View full-size slide

  28. Frame 1
    Message 1

    View full-size slide

  29. Frame 1 Frame 2
    Message 1

    View full-size slide

  30. Frame 1 Frame 2 Frame 3
    Message 1

    View full-size slide

  31. Frame 1 Frame 2 Frame 3 Frame 1
    Message 1
    Frame 2
    Message 2

    View full-size slide

  32. package main
    import (
    "net/http"
    "golang.org/x/net/websocket"
    )
    func main() {
    origin := "http://localhost/"
    ws, err := websocket.Dial("ws://localhost:4000/websocket", "", origin)
    if err != nil {
    panic(err)
    }
    _, err := ws.Write([]byte("Hello?"))
    if err != nil {
    panic(err)
    }
    msg := make([]byte, 512)
    _, err = ws.Read(msg)
    // …

    View full-size slide

  33. Frame 1 Frame 2 Frame 3 Frame 1
    Message 1
    Frame 2
    Message 2

    View full-size slide

  34. Frame 1 Frame 2 Frame 3 Frame 1
    Message 1
    Frame 2
    Message 2
    Read() / Write()
    golang.org/x/net/websocket

    View full-size slide

  35. Frame 1 Frame 2 Frame 3 Frame 1
    Message 1
    Frame 2
    Message 2
    Read() / Write() Read() / Write()
    golang.org/x/net/websocket

    View full-size slide

  36. Frame 1 Frame 2 Frame 3 Frame 1
    Message 1
    Frame 2
    Message 2
    Read() / Write() Read() / Write() Read() / Write()
    golang.org/x/net/websocket

    View full-size slide

  37. Frame 1 Frame 2 Frame 3 Frame 1
    Message 1
    Frame 2
    Message 2
    github.com/gorilla/websocket
    Read() / Write() Read() / Write()
    Read() / Write() Read() / Write() Read() / Write()
    golang.org/x/net/websocket

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  40. 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)
    // …

    View full-size slide

  41. 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)
    // …

    View full-size slide

  42. 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
    }
    }
    }

    View full-size slide

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

    View full-size slide

  44. Backend Clients
    API
    Database
    HTTP

    View full-size slide

  45. Backend Clients
    API
    Database
    HTTP
    Polling

    View full-size slide

  46. Push
    Backend Clients
    API
    Database
    WebSocket
    Bus

    View full-size slide

  47. Push
    Backend Clients
    API
    Database
    WebSocket
    Bus
    Subscribe
    Publish
    State change

    View full-size slide

  48. Push
    Backend Clients
    API
    Database
    WebSocket
    Bus
    Subscribe
    Publish
    State change

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  53. 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
    }

    View full-size slide

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

    View full-size slide

  55. 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
    }

    View full-size slide

  56. 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
    }

    View full-size slide

  57. 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
    - Bad compatibility to the outside world

    View full-size slide

  58. Not Invented
    Here
    Proudly Found
    Elsewhere

    View full-size slide

  59. Thank you.
    @konradreiche

    View full-size slide

  60. Questions?
    @konradreiche

    View full-size slide