├── LICENSE.md ├── README.md ├── example └── main.go └── goose.go /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ian Kent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Goose - Go Server-Sent Events [![GoDoc](https://godoc.org/github.com/ian-kent/goose?status.svg)](https://godoc.org/github.com/ian-kent/goose) 2 | ============================= 3 | 4 | Goose implements Server-Sent Events in Go. 5 | 6 | See [this example](example/main.go). 7 | 8 | ### Licence 9 | 10 | Copyright ©‎ 2014, Ian Kent (http://www.iankent.eu). 11 | 12 | Released under MIT license, see [LICENSE](LICENSE.md) for details. 13 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/gorilla/pat" 8 | "github.com/ian-kent/goose" 9 | ) 10 | 11 | var stream *goose.EventStream 12 | 13 | func main() { 14 | stream = goose.NewEventStream() 15 | 16 | p := pat.New() 17 | p.Path("/events").Methods("GET").HandlerFunc(handler) 18 | p.Path("/notify").Methods("GET").HandlerFunc(someEvent) 19 | http.Handle("/", p) 20 | 21 | err := http.ListenAndServe(":8080", nil) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | } 26 | 27 | func someEvent(w http.ResponseWriter, req *http.Request) { 28 | stream.Notify("data", []byte("Hi!")) 29 | w.WriteHeader(200) 30 | } 31 | 32 | func handler(w http.ResponseWriter, req *http.Request) { 33 | stream.AddReceiver(w) 34 | } 35 | -------------------------------------------------------------------------------- /goose.go: -------------------------------------------------------------------------------- 1 | package goose 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | var ( 14 | // ErrUnableToHijackRequest is returned by AddReceiver if the type 15 | // conversion to http.Hijacker is unsuccessful 16 | ErrUnableToHijackRequest = errors.New("Unable to hijack request") 17 | ) 18 | 19 | // EventStream represents a collection of receivers 20 | type EventStream struct { 21 | mutex *sync.Mutex 22 | receivers map[net.Conn]*EventReceiver 23 | } 24 | 25 | // NewEventStream creates a new event stream 26 | func NewEventStream() *EventStream { 27 | return &EventStream{ 28 | mutex: new(sync.Mutex), 29 | receivers: make(map[net.Conn]*EventReceiver), 30 | } 31 | } 32 | 33 | // EventReceiver represents a hijacked HTTP connection 34 | type EventReceiver struct { 35 | stream *EventStream 36 | conn net.Conn 37 | bufrw *bufio.ReadWriter 38 | } 39 | 40 | // Notify sends the event to all event stream receivers 41 | func (es *EventStream) Notify(event string, bytes []byte) { 42 | // TODO reader? 43 | 44 | lines := strings.Split(string(bytes), "\n") 45 | 46 | data := "" 47 | for _, l := range lines { 48 | data += event + ": " + l + "\n" 49 | } 50 | 51 | sz := len(data) + 1 52 | size := fmt.Sprintf("%X", sz) 53 | 54 | for _, er := range es.receivers { 55 | go er.send(size, data) 56 | } 57 | } 58 | 59 | func (er *EventReceiver) send(size, data string) { 60 | _, err := er.write([]byte(size + "\r\n")) 61 | if err != nil { 62 | return 63 | } 64 | 65 | lines := strings.Split(data, "\n") 66 | for _, ln := range lines { 67 | _, err = er.write([]byte(ln + "\n")) 68 | if err != nil { 69 | return 70 | } 71 | } 72 | er.write([]byte("\r\n")) 73 | } 74 | 75 | func (er *EventReceiver) write(bytes []byte) (int, error) { 76 | n, err := er.bufrw.Write(bytes) 77 | 78 | if err != nil { 79 | er.stream.mutex.Lock() 80 | delete(er.stream.receivers, er.conn) 81 | er.stream.mutex.Unlock() 82 | er.conn.Close() 83 | return n, err 84 | } 85 | 86 | err = er.bufrw.Flush() 87 | if err != nil { 88 | er.stream.mutex.Lock() 89 | delete(er.stream.receivers, er.conn) 90 | er.stream.mutex.Unlock() 91 | er.conn.Close() 92 | } 93 | 94 | return n, err 95 | } 96 | 97 | // AddReceiver hijacks a http.ResponseWriter and attaches it to the event stream 98 | func (es *EventStream) AddReceiver(w http.ResponseWriter) (*EventReceiver, error) { 99 | w.Header().Set("Content-Type", "text/event-stream") 100 | w.Header().Set("Cache-Control", "no-cache") 101 | w.Header().Set("Connection", "keep-alive") 102 | 103 | w.WriteHeader(200) 104 | 105 | hj, ok := w.(http.Hijacker) 106 | if !ok { 107 | return nil, ErrUnableToHijackRequest 108 | } 109 | 110 | hjConn, hjBufrw, err := hj.Hijack() 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | rec := &EventReceiver{es, hjConn, hjBufrw} 116 | 117 | es.mutex.Lock() 118 | es.receivers[hjConn] = rec 119 | es.mutex.Unlock() 120 | 121 | return rec, nil 122 | } 123 | --------------------------------------------------------------------------------