├── LICENSE └── sse ├── event.go └── handler.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Simon Menke 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /sse/event.go: -------------------------------------------------------------------------------- 1 | package sse 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type Event struct { 12 | Id string 13 | Type string 14 | Data string 15 | Retry time.Duration 16 | } 17 | 18 | func (e *Event) is_zero() bool { 19 | return e.Id == "" && e.Type == "" && e.Data == "" && e.Retry == 0 20 | } 21 | 22 | func (e *Event) write_to(w io.Writer) (int, error) { 23 | var ( 24 | buf bytes.Buffer 25 | err error 26 | ) 27 | 28 | if e.is_zero() { 29 | return 0, nil 30 | } 31 | 32 | if e.Id != "" { 33 | _, err = fmt.Fprintf(&buf, "id: %s\n", e.Id) 34 | if err != nil { 35 | return 0, err 36 | } 37 | } 38 | 39 | if e.Type != "" { 40 | _, err = fmt.Fprintf(&buf, "event: %s\n", e.Type) 41 | if err != nil { 42 | return 0, err 43 | } 44 | } 45 | 46 | if e.Retry > 0 { 47 | _, err = fmt.Fprintf(&buf, "retry: %d\n", e.Retry/time.Millisecond) 48 | if err != nil { 49 | return 0, err 50 | } 51 | } 52 | 53 | for _, line := range strings.Split(e.Data, "\n") { 54 | _, err = fmt.Fprintf(&buf, "data: %s\n", line) 55 | if err != nil { 56 | return 0, err 57 | } 58 | } 59 | 60 | _, err = fmt.Fprint(&buf, "\n") 61 | if err != nil { 62 | return 0, err 63 | } 64 | 65 | return w.Write(buf.Bytes()) 66 | } 67 | -------------------------------------------------------------------------------- /sse/handler.go: -------------------------------------------------------------------------------- 1 | package sse 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | var keep_alive_payload = []byte(":keep-alive\n" + 12 | ":xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + 13 | ":xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + 14 | ":xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + 15 | ":xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n") 16 | 17 | type ConnectionError struct { 18 | Err error 19 | } 20 | 21 | func (err *ConnectionError) Error() string { 22 | return fmt.Sprintf("sse: %s", err.Err.Error()) 23 | } 24 | 25 | type EventWriter interface { 26 | // When a connection is reopened it will send a send back the id of the 27 | // last event it saw. 28 | LastEventID() string 29 | 30 | // Write an event to the stream. This will return a ConnectionError when the 31 | // stream was closed. 32 | Write(event *Event) (n int, err error) 33 | 34 | // Close the stream 35 | Close() error 36 | 37 | // Get notified when the underlying connection gets closed. 38 | CloseNotify() <-chan bool 39 | } 40 | 41 | type event_writer_t struct { 42 | last_id string 43 | conn net.Conn 44 | closed chan bool 45 | close_notifiers []chan bool 46 | err error 47 | mtx sync.RWMutex 48 | } 49 | 50 | type write_event_req struct { 51 | event *Event 52 | reply chan write_event_res 53 | } 54 | 55 | type write_event_res struct { 56 | n int 57 | err error 58 | } 59 | 60 | // Hijack the http.ResponseWriter. The Content-Type header is set to 61 | // text/event-stream and the status code is set to 200. 62 | // The caller is responsible for Closing the EventWriter. 63 | func Hijack(w http.ResponseWriter, req *http.Request) (EventWriter, error) { 64 | conn, _, err := w.(http.Hijacker).Hijack() 65 | if err != nil { 66 | return nil, &ConnectionError{err} 67 | } 68 | 69 | ew := &event_writer_t{ 70 | last_id: req.Header.Get("Last-Event-ID"), 71 | conn: conn, 72 | closed: make(chan bool), 73 | } 74 | 75 | err = ew.write_response() 76 | if err != nil { 77 | ew.conn.Close() 78 | return nil, err 79 | } 80 | 81 | go ew.loop() 82 | 83 | return ew, nil 84 | } 85 | 86 | func (ew *event_writer_t) LastEventID() string { 87 | return ew.last_id 88 | } 89 | 90 | func (ew *event_writer_t) Write(event *Event) (int, error) { 91 | ew.mtx.Lock() 92 | defer ew.mtx.Unlock() 93 | 94 | n, err := event.write_to(ew.conn) 95 | return n, ew.set_err(err) 96 | } 97 | 98 | func (ew *event_writer_t) Close() error { 99 | ew.mtx.Lock() 100 | defer ew.mtx.Unlock() 101 | 102 | if ew.closed != nil { 103 | select { 104 | case ew.closed <- true: 105 | default: 106 | } 107 | close(ew.closed) 108 | ew.closed = nil 109 | } 110 | 111 | return ew.close() 112 | } 113 | 114 | func (ew *event_writer_t) CloseNotify() <-chan bool { 115 | ew.mtx.Lock() 116 | defer ew.mtx.Unlock() 117 | 118 | c := make(chan bool, 1) 119 | if ew.closed == nil { 120 | c <- true 121 | close(c) 122 | return c 123 | } 124 | 125 | ew.close_notifiers = append(ew.close_notifiers, c) 126 | return c 127 | } 128 | 129 | func (ew *event_writer_t) loop() { 130 | var ( 131 | next_keep_alive = time.NewTicker(1 * time.Second) 132 | closed = ew.closed 133 | ) 134 | 135 | defer func() { 136 | next_keep_alive.Stop() 137 | }() 138 | 139 | for { 140 | var ( 141 | now = time.Now() 142 | ) 143 | 144 | ew.conn.SetReadDeadline(time.Time{}) 145 | ew.conn.SetWriteDeadline(now.Add(5 * time.Second)) 146 | 147 | select { 148 | 149 | case <-closed: 150 | // closed on local side 151 | return 152 | 153 | case <-next_keep_alive.C: 154 | // did the client hang up? 155 | if err := ew.detect_closed_connection(); err != nil { 156 | ew.close() 157 | return 158 | } 159 | 160 | if err := ew.write_keep_alive(); err != nil { 161 | ew.close() 162 | return 163 | } 164 | 165 | } 166 | } 167 | } 168 | 169 | func (ew *event_writer_t) write_response() error { 170 | resp := &http.Response{ 171 | StatusCode: http.StatusOK, 172 | ProtoMajor: 1, 173 | ProtoMinor: 1, 174 | Header: make(http.Header, 10), 175 | } 176 | resp.Header.Set("Content-Type", "text/event-stream") 177 | resp.Header.Set("Connection", "close") 178 | resp.Header.Set("Date", time.Now().Format(http.TimeFormat)) 179 | 180 | return ew.set_err(resp.Write(ew.conn)) 181 | } 182 | 183 | func (ew *event_writer_t) detect_closed_connection() error { 184 | one := make([]byte, 1) 185 | 186 | ew.conn.SetReadDeadline(time.Now()) 187 | _, err := ew.conn.Read(one) 188 | 189 | if neterr, ok := err.(net.Error); ok && neterr.Timeout() { 190 | err = nil 191 | } 192 | 193 | if err != nil { 194 | err = ew.set_err(err) 195 | return err 196 | } 197 | 198 | ew.conn.SetReadDeadline(time.Time{}) 199 | return nil 200 | } 201 | 202 | func (ew *event_writer_t) write_keep_alive() error { 203 | _, err := ew.conn.Write(keep_alive_payload) 204 | return ew.set_err(err) 205 | } 206 | 207 | func (ew *event_writer_t) close() error { 208 | for _, c := range ew.close_notifiers { 209 | c <- true 210 | close(c) 211 | } 212 | ew.close_notifiers = nil 213 | 214 | return ew.set_err(ew.conn.Close()) 215 | } 216 | 217 | func (ew *event_writer_t) set_err(err error) error { 218 | if err != nil { 219 | err = &ConnectionError{err} 220 | 221 | if ew.err == nil { 222 | ew.err = err 223 | } 224 | } 225 | return err 226 | } 227 | --------------------------------------------------------------------------------