diff --git a/_examples/README.md b/_examples/README.md index 87e3fdc4..d275e7ea 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -343,6 +343,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her - [Write Gzip](http_responsewriter/write-gzip/main.go) - [Stream Writer](http_responsewriter/stream-writer/main.go) - [Transactions](http_responsewriter/transactions/main.go) +- [SSE (Server Side Events)](http_responsewriter/server-side-events/main.go) **NEW** - [SSE (third-party package usage for server-side events)](http_responsewriter/sse-third-party/main.go) > The `context/context#ResponseWriter()` returns an enchament version of a http.ResponseWriter, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris. diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md index d3877ca5..71f153dc 100644 --- a/_examples/README_ZH.md +++ b/_examples/README_ZH.md @@ -341,6 +341,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her - [Write Gzip](http_responsewriter/write-gzip/main.go) - [Stream Writer](http_responsewriter/stream-writer/main.go) - [Transactions](http_responsewriter/transactions/main.go) +- [SSE (Server Side Events)](http_responsewriter/server-side-events/main.go) **NEW** - [SSE (third-party package usage for server-side events)](http_responsewriter/sse-third-party/main.go) > The `context/context#ResponseWriter()` returns an enchament version of a http.ResponseWriter, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris. diff --git a/_examples/http_responsewriter/server-side-events/main.go b/_examples/http_responsewriter/server-side-events/main.go new file mode 100644 index 00000000..b9de5544 --- /dev/null +++ b/_examples/http_responsewriter/server-side-events/main.go @@ -0,0 +1,163 @@ +// Package main shows how to send continuous event messages to the clients through server side events via a broker. +// Read more at: +// https://robots.thoughtbot.com/writing-a-server-sent-events-server-in-go +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/kataras/golog" + "github.com/kataras/iris" +) + +// A Broker holds open client connections, +// listens for incoming events on its Notifier channel +// and broadcast event data to all registered connections. +type Broker struct { + + // Events are pushed to this channel by the main events-gathering routine. + Notifier chan []byte + + // New client connections. + newClients chan chan []byte + + // Closed client connections. + closingClients chan chan []byte + + // Client connections registry. + clients map[chan []byte]bool +} + +// NewBroker returns a new broker factory. +func NewBroker() *Broker { + b := &Broker{ + Notifier: make(chan []byte, 1), + newClients: make(chan chan []byte), + closingClients: make(chan chan []byte), + clients: make(map[chan []byte]bool), + } + + // Set it running - listening and broadcasting events. + go b.listen() + + return b +} + +// Listen on different channels and act accordingly. +func (b *Broker) listen() { + for { + select { + case s := <-b.newClients: + // A new client has connected. + // Register their message channel. + b.clients[s] = true + + golog.Infof("Client added. %d registered clients", len(b.clients)) + + case s := <-b.closingClients: + // A client has dettached and we want to + // stop sending them messages. + delete(b.clients, s) + golog.Warnf("Removed client. %d registered clients", len(b.clients)) + + case event := <-b.Notifier: + // We got a new event from the outside! + // Send event to all connected clients. + for clientMessageChan := range b.clients { + clientMessageChan <- event + } + } + } +} + +func (b *Broker) ServeHTTP(ctx iris.Context) { + // Make sure that the writer supports flushing. + // + flusher, ok := ctx.ResponseWriter().(http.Flusher) + if !ok { + ctx.StatusCode(iris.StatusInternalServerError) + ctx.WriteString("Streaming unsupported!") + return + } + + // Set the headers related to event streaming, you can omit the "application/json" if you send plain text. + // If you developer a go client, you must have: "Accept" : "application/json, text/event-stream" header as well. + ctx.ContentType("application/json, text/event-stream") + ctx.Header("Cache-Control", "no-cache") + ctx.Header("Connection", "keep-alive") + // We also add a Cross-origin Resource Sharing header so browsers on different domains can still connect. + ctx.Header("Access-Control-Allow-Origin", "*") + + // Each connection registers its own message channel with the Broker's connections registry. + messageChan := make(chan []byte) + + // Signal the broker that we have a new connection. + b.newClients <- messageChan + + // Remove this client from the map of connected clients + // when this handler exits. + defer func() { + b.closingClients <- messageChan + }() + + // Listen to connection close and un-register messageChan. + notify := ctx.ResponseWriter().(http.CloseNotifier).CloseNotify() + + go func() { + <-notify + b.closingClients <- messageChan + }() + + // block waiting for messages broadcast on this connection's messageChan. + for { + // Write to the ResponseWriter. + // Server Sent Events compatible. + ctx.Writef("data:%s\n\n", <-messageChan) + // or json: data:{obj}. + + // Flush the data immediatly instead of buffering it for later. + flusher.Flush() + } +} + +type event struct { + Timestamp int64 `json:"timestamp"` + Message string `json:"message"` +} + +func main() { + broker := NewBroker() + + go func() { + for { + time.Sleep(2 * time.Second) + + now := time.Now() + evt := event{ + Timestamp: now.Unix(), + Message: fmt.Sprintf("the time is %v", now.Format(time.RFC1123)), + } + evtBytes, err := json.Marshal(evt) + if err != nil { + golog.Errorf("receiving event failure: %v", err) + continue + } + + golog.Infof("Receiving event") + broker.Notifier <- evtBytes + } + }() + + // Iris web server. + app := iris.New() + app.Get("/", broker.ServeHTTP) + + // http://localhost:8080 + // TIP: If you make use of it inside a web frontend application + // then checkout the "optional.sse.js.html" to use the javascript's API for SSE, + // it will also remove the browser's "loading" indicator while receiving those event messages. + app.Run(iris.Addr(":8080")) +} diff --git a/_examples/http_responsewriter/server-side-events/optional.sse.js.html b/_examples/http_responsewriter/server-side-events/optional.sse.js.html new file mode 100644 index 00000000..8f0307f1 --- /dev/null +++ b/_examples/http_responsewriter/server-side-events/optional.sse.js.html @@ -0,0 +1,17 @@ + + + + + SSE (javascript side) + + + + +

Open the browser's console(F12) and watch for icoming event messages

+ + \ No newline at end of file diff --git a/_examples/webassembly/basic/client/hello_go11beta1.go b/_examples/webassembly/basic/client/hello_go11beta2.go similarity index 85% rename from _examples/webassembly/basic/client/hello_go11beta1.go rename to _examples/webassembly/basic/client/hello_go11beta2.go index d66c7bdb..f62105a7 100644 --- a/_examples/webassembly/basic/client/hello_go11beta1.go +++ b/_examples/webassembly/basic/client/hello_go11beta2.go @@ -1,4 +1,4 @@ -// +build go1.11beta1 +// +build go1.11beta2 package main import ( @@ -8,7 +8,7 @@ import ( ) func main() { - // GOARCH=wasm GOOS=js /home/$yourusername/go1.11beta1/bin/go build -o hello.wasm hello_go11beta1.go + // GOARCH=wasm GOOS=js /home/$yourusername/go1.11beta1/bin/go build -o hello.wasm hello_go11beta2.go js.Global().Get("console").Call("log", "Hello WebAssemply!") message := fmt.Sprintf("Hello, the current time is: %s", time.Now().String()) js.Global().Get("document").Call("getElementById", "hello").Set("innerText", message) diff --git a/_examples/webassembly/basic/main.go b/_examples/webassembly/basic/main.go index 6eb9b5a0..3fc3d5d7 100644 --- a/_examples/webassembly/basic/main.go +++ b/_examples/webassembly/basic/main.go @@ -6,7 +6,7 @@ import ( /* You need to build the hello.wasm first, download the go1.11 and execute the below command: -$ cd client && GOARCH=wasm GOOS=js /home/$yourname/go1.11beta1/bin/go build -o hello.wasm hello_go11beta1.go +$ cd client && GOARCH=wasm GOOS=js /home/$yourname/go1.11beta1/bin/go build -o hello.wasm hello_go11beta2.go */ func main() {