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

Server-Sent Events in Go

Oleg Kovalov
February 20, 2020

Server-Sent Events in Go

Oleg Kovalov

February 20, 2020
Tweet

More Decks by Oleg Kovalov

Other Decks in Programming

Transcript

  1. Server-Sent Events
    WARSAW, Feb 2020
    Oleg Kovalov
    Allegro
    https://olegk.dev

    View full-size slide

  2. Me
    - Gopher for ~4 years
    - Open source contributor
    - Engineer at allegro.pl core team
    Website: https://olegk.dev
    Twitter: @oleg_kovalov
    Github: @cristaloleg
    2

    View full-size slide

  3. What is SSE?
    3

    View full-size slide

  4. - Senior Software Engineer
    What is SSE?
    4

    View full-size slide

  5. - Senior Software Engineer
    - Server-Sent Events
    What is SSE?
    5

    View full-size slide

  6. - Senior Software Engineer
    - Server-Sent Events
    - Streaming SIMD Extensions
    What is SSE?
    6

    View full-size slide

  7. - Senior Software Engineer
    - Server-Sent Events
    - Streaming SIMD Extensions
    - Shanghai Stock Exchange
    - Small-scale enterprise
    What is SSE?
    7

    View full-size slide

  8. - Senior Software Engineer
    - Server-Sent Events
    - Streaming SIMD Extensions
    - Shanghai Stock Exchange
    - Small-scale enterprise
    - Skyrim Special Edition
    - ...
    What is SSE?
    8

    View full-size slide

  9. 21st century means real-time
    9

    View full-size slide

  10. Real-time data delivery
    11

    View full-size slide

  11. - Pull
    - Hey, Server, do you have something?
    Real-time data delivery
    12

    View full-size slide

  12. - Pull
    - Hey, Server, do you have something?
    - Push
    - Hey, Client, I have something!
    Real-time data delivery
    13

    View full-size slide

  13. - Pull
    - Hey, Server, do you have something?
    - Push
    - Hey, Client, I have something!
    - Bidirectional
    - Server <-> Client
    Real-time data delivery
    14

    View full-size slide

  14. - Pull
    - Hey, Server, do you have something?
    - Push
    - Hey, Client, I have something!
    - Bidirectional
    - Server <-> Client
    - Unidirectional
    - Server -> Client or Client -> Server
    Real-time data delivery
    15

    View full-size slide

  15. - Pull
    - Hey, Server, do you have something?
    - Push
    - Hey, Client, I have something!
    - Bidirectional
    - Server <-> Client
    - Unidirectional
    - Server -> Client or Client -> Server
    - ...
    Real-time data delivery
    16

    View full-size slide

  16. There are few ways
    17

    View full-size slide

  17. - Long/short Polling
    There are few ways
    18

    View full-size slide

  18. - Long/short Polling
    - WebSocket
    There are few ways
    19

    View full-size slide

  19. - Long/short Polling
    - WebSocket
    - Server-Sent Events
    There are few ways
    20

    View full-size slide

  20. - Long/short Polling
    - WebSocket
    - Server-Sent Events
    - MQTT and sooooo on...
    There are few ways
    21

    View full-size slide

  21. - Made in 2008
    WebSocket
    23

    View full-size slide

  22. - Made in 2008
    - Bidirectional Server-Client protocol
    WebSocket
    24

    View full-size slide

  23. - Made in 2008
    - Bidirectional Server-Client protocol
    - Supported by all browsers (even IE)
    WebSocket
    25

    View full-size slide

  24. WebSocket
    26
    - Made in 2008
    - Bidirectional Server-Client protocol
    - Supported by all browsers (even IE)
    - And binary data

    View full-size slide

  25. Server-Sent Events?
    27

    View full-size slide

  26. - Made in 2006
    Server-Sent Events?
    28

    View full-size slide

  27. - Made in 2006
    - Simple Server-Client protocol over HTTP
    Server-Sent Events?
    29

    View full-size slide

  28. - Made in 2006
    - Simple Server-Client protocol over HTTP
    - Only Server -> Client direction
    Server-Sent Events?
    30

    View full-size slide

  29. - Made in 2006
    - Simple Server-Client protocol over HTTP
    - Only Server -> Client direction
    - Supported by all browsers (sorry, no IE)
    Server-Sent Events?
    31

    View full-size slide

  30. - Made in 2006
    - Simple Server-Client protocol over HTTP
    - Only Server -> Client direction
    - Supported by all browsers (sorry, no IE)
    - No binary data
    Server-Sent Events?
    32

    View full-size slide

  31. - Made in 2006
    - Simple Server-Client protocol over HTTP
    - Only Server -> Client direction
    - Supported by all browsers (sorry, no IE)
    - No binary data
    Debut in Opera, September 1, 2006
    Server-Sent Events?
    33

    View full-size slide

  32. - GET /sse
    Flow
    35

    View full-size slide

  33. - GET /sse
    -
    Flow
    36

    View full-size slide

  34. - GET /sse
    -
    - Content-Type: “text/event-stream”
    Flow
    37

    View full-size slide

  35. Flow
    - GET /sse
    -
    - Content-Type: “text/event-stream”
    -
    38

    View full-size slide

  36. Flow
    - GET /sse
    -
    - Content-Type: “text/event-stream”
    -
    Other checks if you want, it stills a HTTP
    39

    View full-size slide

  37. SSE protocol is simple
    40

    View full-size slide

  38. - ID - uniqueness
    SSE protocol is simple
    41

    View full-size slide

  39. - ID - uniqueness
    - Event - as a type
    SSE protocol is simple
    42

    View full-size slide

  40. - ID - uniqueness
    - Event - as a type
    - Data - main part
    SSE protocol is simple
    43

    View full-size slide

  41. - ID - uniqueness
    - Event - as a type
    - Data - main part
    - Retry - refresh rate
    SSE protocol is simple
    44

    View full-size slide

  42. - ID - uniqueness
    - Event - as a type
    - Data - main part
    - Retry - refresh rate
    id: 54\n
    event: hey-you\n
    data: yes that’s a data\n
    data: \n\n
    SSE protocol is simple
    45

    View full-size slide

  43. - `Last-Event-ID` header
    - A last known ID for client
    - Decide on your own what to do next
    A cozy thing
    46

    View full-size slide

  44. Let’s upgrade to SSE
    47

    View full-size slide

  45. http.HandleFunc("/sse", func(w http.ResponseWriter, r *http.Request) {
    Let’s upgrade to SSE
    48

    View full-size slide

  46. http.HandleFunc("/sse", func(w http.ResponseWriter, r *http.Request) {
    stream, err := sse.UpgradeHTTP(r, w)
    if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
    }
    Let’s upgrade to SSE
    49

    View full-size slide

  47. http.HandleFunc("/sse", func(w http.ResponseWriter, r *http.Request) {
    stream, err := sse.UpgradeHTTP(r, w)
    if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
    }
    var data struct {
    Text string `json:"text"`
    Timestamp int64 `json:"ts"`
    }
    Let’s upgrade to SSE
    50

    View full-size slide

  48. http.HandleFunc("/sse", func(w http.ResponseWriter, r *http.Request) {
    stream, err := sse.UpgradeHTTP(r, w)
    if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
    }
    var data struct {
    Text string `json:"text"`
    Timestamp int64 `json:"ts"`
    }
    for {
    data.Timestamp = time.Now().Unix()
    stream.WriteJSON(data)
    time.Sleep(time.Second)
    }
    })
    Let’s upgrade to SSE
    51

    View full-size slide

  49. func UpgradeHTTP(r *http.Request, w http.ResponseWriter) (*Stream, error) {
    Inside the UpgradeHTTP
    52

    View full-size slide

  50. func UpgradeHTTP(r *http.Request, w http.ResponseWriter) (*Stream, error) {
    hj, ok := w.(http.Hijacker)
    if !ok {
    return nil, ErrNotHijacker
    }
    Inside the UpgradeHTTP
    53

    View full-size slide

  51. func UpgradeHTTP(r *http.Request, w http.ResponseWriter) (*Stream, error) {
    hj, ok := w.(http.Hijacker)
    if !ok {
    return nil, ErrNotHijacker
    }
    // Hijack() (net.Conn, *bufio.ReadWriter, error)
    //
    // The Hijacker interface is implemented by ResponseWriters
    // that allow an HTTP handler to take over the connection.
    Inside the UpgradeHTTP
    54

    View full-size slide

  52. func UpgradeHTTP(r *http.Request, w http.ResponseWriter) (*Stream, error) {
    hj, ok := w.(http.Hijacker)
    if !ok {
    return nil, ErrNotHijacker
    }
    // Hijack() (net.Conn, *bufio.ReadWriter, error)
    //
    // The Hijacker interface is implemented by ResponseWriters
    // that allow an HTTP handler to take over the connection.
    //
    // After a call to Hijack, the original Request.Body must not
    // be used.
    Inside the UpgradeHTTP
    55

    View full-size slide

  53. func UpgradeHTTP(r *http.Request, w http.ResponseWriter) (*Stream, error) {
    hj, ok := w.(http.Hijacker)
    if !ok {
    return nil, ErrNotHijacker
    }
    _, bw, err := hj.Hijack()
    if err != nil {
    retu rn nil, err
    }
    Inside the UpgradeHTTP
    56

    View full-size slide

  54. func UpgradeHTTP(r *http.Request, w http.ResponseWriter) (*Stream, error) {
    hj, ok := w.(http.Hijacker)
    if !ok {
    return nil, ErrNotHijacker
    }
    _, bw, err := hj.Hijack()
    if err != nil {
    retu rn nil, err
    }
    httpWriteResponseUpgrade(bw.Writer)
    Inside the UpgradeHTTP
    57

    View full-size slide

  55. func UpgradeHTTP(r *http.Request, w http.ResponseWriter) (*Stream, error) {
    hj, ok := w.(http.Hijacker)
    if !ok {
    return nil, ErrNotHijacker
    }
    _, bw, err := hj.Hijack()
    if err != nil {
    retu rn nil, err
    }
    httpWriteResponseUpgrade(bw.Writer)
    if err := bw.Flush(); err != nil {
    return nil, err
    }
    return &Stream{bw: bw}, nil
    }
    Inside the UpgradeHTTP
    58

    View full-size slide

  56. Improvise.
    59

    View full-size slide

  57. Improvise. Adopt.
    60

    View full-size slide

  58. Improvise. Adopt. BufferedWriter.
    61

    View full-size slide

  59. const (
    textStatusLine = "HTTP/1.1 200\r\n"
    colonAndSpace = ": "
    )
    Improvise. Adopt. BufferedWriter.
    62

    View full-size slide

  60. const (
    textStatusLine = "HTTP/1.1 200\r\n"
    colonAndSpace = ": "
    )
    func httpWriteResponseUpgrade(bw *bufio.Writer) {
    bw.WriteString(textStatusLine)
    httpWriteHeader(bw, "Cache-Control", "no-cache")
    httpWriteHeader(bw, "Content-Type", "text/event-stream")
    bw.WriteString(crlf)
    }
    Improvise. Adopt. BufferedWriter.
    63

    View full-size slide

  61. const (
    textStatusLine = "HTTP/1.1 200\r\n"
    colonAndSpace = ": "
    )
    func httpWriteResponseUpgrade(bw *bufio.Writer) {
    bw.WriteString(textStatusLine)
    httpWriteHeader(bw, "Cache-Control", "no-cache")
    httpWriteHeader(bw, "Content-Type", "text/event-stream")
    bw.WriteString(crlf)
    }
    func httpWriteHeader(bw *bufio.Writer, key, value string) {
    bw.WriteString(key)
    bw.WriteString(colonAndSpace)
    bw.WriteString(value)
    bw.WriteString(crlf)
    }
    Improvise. Adopt. BufferedWriter.
    64

    View full-size slide

  62. Stream from a user perspective
    65

    View full-size slide

  63. func UpgradeHTTP(*http.Request, http.ResponseWriter) (*Stream, error) {...}
    Stream from a user perspective
    66

    View full-size slide

  64. func UpgradeHTTP(*http.Request, http.ResponseWriter) (*Stream, error) {...}
    type Stream struct {
    bw *bufio.ReadWriter
    }
    Stream from a user perspective
    67

    View full-size slide

  65. func UpgradeHTTP(*http.Request, http.ResponseWriter) (*Stream, error) {...}
    type Stream struct {
    bw *bufio.ReadWriter
    }
    func (s *Stream) SetEvent(event string) error {
    data := []byte("event:" + event + "\n")
    _, err := s.bw.Write(data)
    return err
    }
    Stream from a user perspective
    68

    View full-size slide

  66. func UpgradeHTTP(*http.Request, http.ResponseWriter) (*Stream, error) {...}
    type Stream struct {
    bw *bufio.ReadWriter
    }
    func (s *Stream) SetEvent(event string) error {
    data := []byte("event:" + event + "\n")
    _, err := s.bw.Write(data)
    return err
    }
    func (s *Stream) WriteBytes(data []byte) error {
    return s.writeData(data)
    }
    Stream from a user perspective
    69

    View full-size slide

  67. func UpgradeHTTP(*http.Request, http.ResponseWriter) (*Stream, error) {...}
    type Stream struct {
    bw *bufio.ReadWriter
    }
    func (s *Stream) SetEvent(event string) error {
    data := []byte("event:" + event + "\n")
    _, err := s.bw.Write(data)
    return err
    }
    func (s *Stream) WriteBytes(data []byte) error {
    return s.writeData(data)
    }
    and other methods: SetRetry, WriteJSON, WriteFloat...
    Stream from a user perspective
    70

    View full-size slide

  68. BRUTAL MINIMALISM
    71

    View full-size slide




  69. SSE Demo
    <br/>// next slide, I promise<br/>


    Waiting for data…



    BRUTAL MINIMALISM
    72

    View full-size slide




  70. SSE Demo
    <br/>// next slide, I promise<br/>


    Waiting for data…



    BRUTAL MINIMALISM
    73
    Google: This is a motherf***ing website.

    View full-size slide

  71. A bit of JavaScript, no node_modules!
    74

    View full-size slide

  72. let contentbox = document.querySelector("#contentbox")
    A bit of JavaScript, no node_modules!
    75

    View full-size slide

  73. let contentbox = document.querySelector("#contentbox")
    let src = new EventSource("http://127.0.0.1:31337/sse")
    src.addEventListener("close", e => {
    src.close();
    contentbox.innerText += "close connection\n";
    });
    A bit of JavaScript, no node_modules!
    76

    View full-size slide

  74. let contentbox = document.querySelector("#contentbox")
    let src = new EventSource("http://127.0.0.1:31337/sse")
    src.addEventListener("close", e => {
    src.close();
    contentbox.innerText += "close connection\n";
    });
    src.addEventListener("message", e => {
    let data = JSON.parse(e.data)
    contentbox.innerText += data.time + "\n";
    });
    A bit of JavaScript, no node_modules!
    77

    View full-size slide


  75. Fingers crossed Demo time
    78

    View full-size slide

  76. Who needs TCP when we’re almost in HTTP3 era?
    79

    View full-size slide

  77. - Better resource control
    Who needs TCP when we’re almost in HTTP3 era?
    80

    View full-size slide

  78. Who needs TCP when we’re almost in HTTP3 era?
    81
    - Better resource control
    - Less allocations

    View full-size slide

  79. Who needs TCP when we’re almost in HTTP3 era?
    82
    - Better resource control
    - Less allocations
    - Mechanical sympathy

    View full-size slide

  80. Who needs TCP when we’re almost in HTTP3 era?
    83
    - Better resource control
    - Less allocations
    - Mechanical sympathy
    See Github:
    - valyala/fasthttp - HTTP/1.1
    - gobwas/ws - WebSocket

    View full-size slide

  81. Simple Go HTTP server
    ...
    func main() {
    http.Handle("/", yoHandler)
    http.ListenAndServe(":31337", nil)
    }
    func yoHandler(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hey ʕ◔ϖ◔ʔ")
    }
    84

    View full-size slide

  82. // Serve accepts incoming connections on the Listener l, creating a
    // new service goroutine for each. The service goroutines read requests and
    // then call srv.Handler to reply to them.
    //
    func (srv *Server) Serve(l net.Listener) error {
    // ...
    for {
    rw, e := l.Accept()
    // ...
    c := srv.newConn(rw)
    go c.serve(connCtx)
    }
    }
    But what happens inside?
    85

    View full-size slide

  83. func main() {
    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
    log.Fatal(err)
    }
    defer ln.Close()
    for {
    conn, err := ln.Accept()
    if err != nil {
    log.Println(err)
    continue
    }
    go handle(conn)
    }
    }
    Well, it’s just a TCP server
    86

    View full-size slide

  84. func main() {
    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
    log.Fatal(err)
    }
    defer ln.Close()
    for {
    conn, err := ln.Accept()
    if err != nil {
    log.Println(err)
    continue
    }
    go handle(conn) // or just an Upgrade!
    }
    }
    Well, it’s just a TCP server
    87

    View full-size slide

  85. func (u Upgrader) Upgrade(conn io.ReadWriter) (*Stream, error) {
    br := bufio.NewReaderSize(conn, 1000)
    bw := bufio.NewWriterSize(conn, 1000)
    rl, err := readLine(br)
    if err != nil {
    return nil, err
    }
    req, err := httpParseRequestLine(rl)
    if err != nil {
    return nil, err
    }
    // ...
    }
    Low-level Upgrader
    88

    View full-size slide

  86. func (u Upgrader) Upgrade(conn io.ReadWriter) (*Stream, error) {
    br := bufio.NewReaderSize(conn, 1000)
    bw := bufio.NewWriterSize(conn, 1000)
    rl, err := readLine(br)
    if err != nil {
    return nil, err
    }
    req, err := httpParseRequestLine(rl)
    if err != nil {
    return nil, err
    }
    // ...
    }
    Low-level Upgrader
    89

    View full-size slide

  87. func (u Upgrader) Upgrade(conn io.ReadWriter) (*Stream, error) {
    // ...
    for err == nil {
    line, errRead := readLine(br)
    if errRead != nil {
    return nil, errRead
    }
    if len(line) == 0 {
    break // Blank line, no more lines to read.
    }
    k, v, ok := httpParseHeaderLine(line)
    if !ok {
    err = errors.New("ErrMalformedRequest"); break
    }
    // ...
    }
    }
    Low-level Upgrader (cont.)
    90

    View full-size slide

  88. Wanna cool thing?
    91

    View full-size slide

  89. - That’s basically all
    Wanna cool thing?
    92

    View full-size slide

  90. - That’s basically all
    - Stream works with bufio.Writer
    Wanna cool thing?
    93

    View full-size slide

  91. - That’s basically all
    - Stream works with bufio.Writer
    - Which is independent of a type of a Writer
    Wanna cool thing?
    94

    View full-size slide

  92. Where is the code Lebowski?
    95

    View full-size slide

  93. - Good question
    Where is the code Lebowski?
    96

    View full-size slide

  94. - Good question
    - Partially available
    - https://github.com/cristalhq/sse
    Where is the code Lebowski?
    97

    View full-size slide

  95. - Good question
    - Partially available
    - https://github.com/cristalhq/sse
    - Eventually everything will be on Github
    Where is the code Lebowski?
    98

    View full-size slide

  96. - Good question
    - Partially available
    - https://github.com/cristalhq/sse
    - Eventually everything will be on Github
    - I mean code...
    Where is the code Lebowski?
    99

    View full-size slide

  97. cristaltech? cristalhq? wut?
    100

    View full-size slide

  98. - Make better open source
    cristaltech? cristalhq? wut?
    101

    View full-size slide

  99. - Make better open source
    - Simple API
    cristaltech? cristalhq? wut?
    102

    View full-size slide

  100. - Make better open source
    - Simple API
    - No dependencies (mostly)
    cristaltech? cristalhq? wut?
    103

    View full-size slide

  101. - Make better open source
    - Simple API
    - No dependencies (mostly)
    - Learn new things & have fun
    cristaltech? cristalhq? wut?
    104

    View full-size slide

  102. - Make better open source
    - Simple API
    - No dependencies (mostly)
    - Learn new things & have fun
    - Don’t forget to rest sometimes
    cristaltech? cristalhq? wut?
    105

    View full-size slide

  103. - Aliaksandr Valialkin (fasthttp)
    - Sergey Kamardin (ws)
    - One of the best Go libraries (IMO, but who cares)
    https://github.com/valyala/fasthttp
    https://github.com/gobwas/ws
    Thanks
    106

    View full-size slide

  104. That’s all folks
    Thank you
    Questions?
    Twitter: @oleg_kovalov
    Github: @cristaloleg

    View full-size slide