sse-encoder.go (2256B)
1 // Copyright 2014 Manu Martinez-Almeida. All rights reserved. 2 // Use of this source code is governed by a MIT style 3 // license that can be found in the LICENSE file. 4 5 package sse 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 "reflect" 13 "strconv" 14 "strings" 15 ) 16 17 // Server-Sent Events 18 // W3C Working Draft 29 October 2009 19 // http://www.w3.org/TR/2009/WD-eventsource-20091029/ 20 21 const ContentType = "text/event-stream" 22 23 var contentType = []string{ContentType} 24 var noCache = []string{"no-cache"} 25 26 var fieldReplacer = strings.NewReplacer( 27 "\n", "\\n", 28 "\r", "\\r") 29 30 var dataReplacer = strings.NewReplacer( 31 "\n", "\ndata:", 32 "\r", "\\r") 33 34 type Event struct { 35 Event string 36 Id string 37 Retry uint 38 Data interface{} 39 } 40 41 func Encode(writer io.Writer, event Event) error { 42 w := checkWriter(writer) 43 writeId(w, event.Id) 44 writeEvent(w, event.Event) 45 writeRetry(w, event.Retry) 46 return writeData(w, event.Data) 47 } 48 49 func writeId(w stringWriter, id string) { 50 if len(id) > 0 { 51 w.WriteString("id:") 52 fieldReplacer.WriteString(w, id) 53 w.WriteString("\n") 54 } 55 } 56 57 func writeEvent(w stringWriter, event string) { 58 if len(event) > 0 { 59 w.WriteString("event:") 60 fieldReplacer.WriteString(w, event) 61 w.WriteString("\n") 62 } 63 } 64 65 func writeRetry(w stringWriter, retry uint) { 66 if retry > 0 { 67 w.WriteString("retry:") 68 w.WriteString(strconv.FormatUint(uint64(retry), 10)) 69 w.WriteString("\n") 70 } 71 } 72 73 func writeData(w stringWriter, data interface{}) error { 74 w.WriteString("data:") 75 switch kindOfData(data) { 76 case reflect.Struct, reflect.Slice, reflect.Map: 77 err := json.NewEncoder(w).Encode(data) 78 if err != nil { 79 return err 80 } 81 w.WriteString("\n") 82 default: 83 dataReplacer.WriteString(w, fmt.Sprint(data)) 84 w.WriteString("\n\n") 85 } 86 return nil 87 } 88 89 func (r Event) Render(w http.ResponseWriter) error { 90 r.WriteContentType(w) 91 return Encode(w, r) 92 } 93 94 func (r Event) WriteContentType(w http.ResponseWriter) { 95 header := w.Header() 96 header["Content-Type"] = contentType 97 98 if _, exist := header["Cache-Control"]; !exist { 99 header["Cache-Control"] = noCache 100 } 101 } 102 103 func kindOfData(data interface{}) reflect.Kind { 104 value := reflect.ValueOf(data) 105 valueType := value.Kind() 106 if valueType == reflect.Ptr { 107 valueType = value.Elem().Kind() 108 } 109 return valueType 110 }