Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

What is SSE? 3

Slide 4

Slide 4 text

- Senior Software Engineer What is SSE? 4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

21st century means real-time 9

Slide 10

Slide 10 text

10 Wikipedia

Slide 11

Slide 11 text

Real-time data delivery 11

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

- 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

Slide 16

Slide 16 text

- 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

Slide 17

Slide 17 text

There are few ways 17

Slide 18

Slide 18 text

- Long/short Polling There are few ways 18

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

WebSocket 22

Slide 23

Slide 23 text

- Made in 2008 WebSocket 23

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Server-Sent Events? 27

Slide 28

Slide 28 text

- Made in 2006 Server-Sent Events? 28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

- 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

Slide 33

Slide 33 text

- 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

Slide 34

Slide 34 text

Flow 34

Slide 35

Slide 35 text

- GET /sse Flow 35

Slide 36

Slide 36 text

- GET /sse - Flow 36

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

SSE protocol is simple 40

Slide 41

Slide 41 text

- ID - uniqueness SSE protocol is simple 41

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

- 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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Let’s upgrade to SSE 47

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Improvise. 59

Slide 60

Slide 60 text

Improvise. Adopt. 60

Slide 61

Slide 61 text

Improvise. Adopt. BufferedWriter. 61

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Stream from a user perspective 65

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

BRUTAL MINIMALISM 71

Slide 72

Slide 72 text

SSE Demo // next slide, I promise
Waiting for data…
BRUTAL MINIMALISM 72

Slide 73

Slide 73 text

SSE Demo // next slide, I promise
Waiting for data…
BRUTAL MINIMALISM 73 Google: This is a motherf***ing website.

Slide 74

Slide 74 text

A bit of JavaScript, no node_modules! 74

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Fingers crossed Demo time 78

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

Wanna cool thing? 91

Slide 92

Slide 92 text

- That’s basically all Wanna cool thing? 92

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

Where is the code Lebowski? 95

Slide 96

Slide 96 text

- Good question Where is the code Lebowski? 96

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

cristaltech? cristalhq? wut? 100

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

- 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

Slide 107

Slide 107 text

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