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 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 Slide

  3. What is SSE?
    3

    View Slide

  4. - Senior Software Engineer
    What is SSE?
    4

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

  9. 21st century means real-time
    9

    View Slide

  10. 10
    Wikipedia

    View Slide

  11. Real-time data delivery
    11

    View Slide

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

    View Slide

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

    View Slide

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

    View 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
    15

    View Slide

  16. - 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 Slide

  17. There are few ways
    17

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. WebSocket
    22

    View Slide

  23. - Made in 2008
    WebSocket
    23

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  27. Server-Sent Events?
    27

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. - 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 Slide

  32. - 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 Slide

  33. - 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 Slide

  34. Flow
    34

    View Slide

  35. - GET /sse
    Flow
    35

    View Slide

  36. - GET /sse
    -
    Flow
    36

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  40. SSE protocol is simple
    40

    View Slide

  41. - ID - uniqueness
    SSE protocol is simple
    41

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. - 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 Slide

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

    View Slide

  47. Let’s upgrade to SSE
    47

    View Slide

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

    View Slide

  49. 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 Slide

  50. 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 Slide

  51. 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 Slide

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

    View Slide

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

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

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

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

  57. 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 Slide

  58. 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 Slide

  59. Improvise.
    59

    View Slide

  60. Improvise. Adopt.
    60

    View Slide

  61. Improvise. Adopt. BufferedWriter.
    61

    View Slide

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

    View Slide

  63. 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 Slide

  64. 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 Slide

  65. Stream from a user perspective
    65

    View Slide

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

    View Slide

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

    View Slide

  68. 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 Slide

  69. 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 Slide

  70. 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 Slide

  71. BRUTAL MINIMALISM
    71

    View Slide




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


    Waiting for data…



    BRUTAL MINIMALISM
    72

    View Slide




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


    Waiting for data…



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

    View Slide

  74. A bit of JavaScript, no node_modules!
    74

    View Slide

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

    View Slide

  76. 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 Slide

  77. 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 Slide


  78. Fingers crossed Demo time
    78

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  83. 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 Slide

  84. 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 Slide

  85. // 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 Slide

  86. 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 Slide

  87. 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 Slide

  88. 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 Slide

  89. 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 Slide

  90. 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 Slide

  91. Wanna cool thing?
    91

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  95. Where is the code Lebowski?
    95

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  99. - 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 Slide

  100. cristaltech? cristalhq? wut?
    100

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  105. - 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 Slide

  106. - 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 Slide

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

    View Slide