From 1f11541011d37ee422480688962916b5744878bd Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Wed, 13 May 2015 16:44:44 +0200 Subject: [PATCH] Updates realtime-advanced demo --- examples/realtime-advanced/limit.go | 7 +- examples/realtime-advanced/main.go | 20 ++- .../resources/room_login.templ.html | 86 +++++++++-- .../resources/static/prismjs.min.css | 137 ++++++++++++++++++ .../resources/static/prismjs.min.js | 5 + .../resources/static/realtime.js | 22 ++- examples/realtime-advanced/stats.go | 11 +- 7 files changed, 263 insertions(+), 25 deletions(-) create mode 100644 examples/realtime-advanced/resources/static/prismjs.min.css create mode 100644 examples/realtime-advanced/resources/static/prismjs.min.js diff --git a/examples/realtime-advanced/limit.go b/examples/realtime-advanced/limit.go index 5d9ec3be..022c936c 100644 --- a/examples/realtime-advanced/limit.go +++ b/examples/realtime-advanced/limit.go @@ -4,17 +4,16 @@ import ( "log" "github.com/gin-gonic/gin" + "github.com/manucorporat/stats" ) -import "github.com/manucorporat/stats" - var ips = stats.New() func ratelimit(c *gin.Context) { ip := c.ClientIP() value := uint64(ips.Add(ip, 1)) - if value >= 400 { - if value%400 == 0 { + if value >= 1000 { + if value%1000 == 0 { log.Printf("BlockedIP:%s Requests:%d\n", ip, value) } c.AbortWithStatus(401) diff --git a/examples/realtime-advanced/main.go b/examples/realtime-advanced/main.go index 729f0e81..fb6db719 100644 --- a/examples/realtime-advanced/main.go +++ b/examples/realtime-advanced/main.go @@ -1,13 +1,24 @@ package main import ( + "fmt" "io" + "runtime" "time" "github.com/gin-gonic/gin" + "github.com/manucorporat/stats" ) +var messages = stats.New() + func main() { + nuCPU := runtime.NumCPU() + runtime.GOMAXPROCS(nuCPU) + fmt.Printf("Running with %d CPUs\n", nuCPU) + + gin.SetMode(gin.ReleaseMode) + router := gin.New() router.Use(ratelimit, gin.Recovery(), gin.Logger()) @@ -19,7 +30,7 @@ func main() { //router.DELETE("/room/:roomid", roomDELETE) router.GET("/stream/:roomid", streamRoom) - router.Run(":8080") + router.Run("127.0.0.1:8080") } func index(c *gin.Context) { @@ -29,6 +40,9 @@ func index(c *gin.Context) { func roomGET(c *gin.Context) { roomid := c.ParamValue("roomid") userid := c.FormValue("nick") + if len(userid) > 13 { + userid = userid[0:12] + "..." + } c.HTML(200, "room_login.templ.html", gin.H{ "roomid": roomid, "nick": userid, @@ -42,7 +56,7 @@ func roomPOST(c *gin.Context) { nick := c.FormValue("nick") message := c.PostFormValue("message") - if len(message) > 200 || len(nick) > 20 { + if len(message) > 200 || len(nick) > 13 { c.JSON(400, gin.H{ "status": "failed", "error": "the message or nickname is too long", @@ -54,6 +68,7 @@ func roomPOST(c *gin.Context) { "nick": nick, "message": message, } + messages.Add("inbound", 1) room(roomid).Submit(post) c.JSON(200, post) } @@ -73,6 +88,7 @@ func streamRoom(c *gin.Context) { c.Stream(func(w io.Writer) bool { select { case msg := <-listener: + messages.Add("outbound", 1) c.SSEvent("message", msg) case <-ticker.C: c.SSEvent("stats", Stats()) diff --git a/examples/realtime-advanced/resources/room_login.templ.html b/examples/realtime-advanced/resources/room_login.templ.html index 02bc776c..ce7b1362 100644 --- a/examples/realtime-advanced/resources/room_login.templ.html +++ b/examples/realtime-advanced/resources/room_login.templ.html @@ -19,13 +19,41 @@ + + + + +
@@ -33,10 +61,8 @@

Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection.

The chat and the charts data is provided in realtime using the SSE implemention of Gin Framework.

- {{if not .nick}}
- {{end}} -
+
@@ -49,17 +75,22 @@ {{if .nick}} -
- -
-
- +
+ +
+
{{.nick}}
+ +
+ {{end}} - {{if not .nick}}
+ {{if .nick}} +

Inbound/Outbound

+
+ {{else}}
Join the SSE real-time chat
@@ -70,13 +101,14 @@
+ {{end}}
- {{end}}
+

Realtime server Go stats

Number of Goroutines

@@ -96,9 +128,41 @@

+
+

Source code

+
+ +

Server-side (Go)

+
func streamRoom(c *gin.Context) {
+    roomid := c.ParamValue("roomid")
+    listener := openListener(roomid)
+    statsTicker := time.NewTicker(1 * time.Second)
+    defer closeListener(roomid, listener)
+    defer statsTicker.Stop()
+
+    c.Stream(func(w io.Writer) bool {
+        select {
+        case msg := <-listener:
+            c.SSEvent("message", msg)
+        case <-statsTicker.C:
+            c.SSEvent("stats", Stats())
+        }
+        return true
+    })
+}
+
+
+

Client-side (JS)

+
function StartSSE(roomid) {
+    var source = new EventSource('/stream/'+roomid);
+    source.addEventListener('message', newChatMessage, false);
+    source.addEventListener('stats', stats, false);
+}
+
+

diff --git a/examples/realtime-advanced/resources/static/prismjs.min.css b/examples/realtime-advanced/resources/static/prismjs.min.css new file mode 100644 index 00000000..0d9d8fb1 --- /dev/null +++ b/examples/realtime-advanced/resources/static/prismjs.min.css @@ -0,0 +1,137 @@ +/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+go */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + diff --git a/examples/realtime-advanced/resources/static/prismjs.min.js b/examples/realtime-advanced/resources/static/prismjs.min.js new file mode 100644 index 00000000..a6855a78 --- /dev/null +++ b/examples/realtime-advanced/resources/static/prismjs.min.js @@ -0,0 +1,5 @@ +/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+go */ +self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{};var Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var s="";for(var o in i.attributes)s+=o+'="'+(i.attributes[o]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+s+">"+i.content+""},!self.document)return self.addEventListener?(self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),self.close()},!1),self.Prism):self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);; +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/("|')(\\\n|\\?.)*?\1/,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":{pattern:/[a-z0-9_]+\(/i,inside:{punctuation:/\(/}},number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|~|\^|%/,ignore:/&(lt|gt|amp);/i,punctuation:/[{}[\];(),.:]/};; +Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|get|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|-?Infinity)\b/,"function":/(?!\d)[a-z0-9_$]+(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/i,inside:{tag:{pattern:/|<\/script>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript},alias:"language-javascript"}});; +Prism.languages.go=Prism.languages.extend("clike",{keyword:/\b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,builtin:/\b(bool|byte|complex(64|128)|error|float(32|64)|rune|string|u?int(8|16|32|64|)|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(ln)?|real|recover)\b/,"boolean":/\b(_|iota|nil|true|false)\b/,operator:/([(){}\[\]]|[*\/%^!]=?|\+[=+]?|-[>=-]?|\|[=|]?|>[=>]?|<(<|[=-])?|==?|&(&|=|^=?)?|\.(\.\.)?|[,;]|:=?)/,number:/\b(-?(0x[a-f\d]+|(\d+\.?\d*|\.\d+)(e[-+]?\d+)?)i?)\b/i,string:/("|'|`)(\\?.|\r|\n)*?\1/}),delete Prism.languages.go["class-name"];; diff --git a/examples/realtime-advanced/resources/static/realtime.js b/examples/realtime-advanced/resources/static/realtime.js index 1548ab87..7872dcb8 100644 --- a/examples/realtime-advanced/resources/static/realtime.js +++ b/examples/realtime-advanced/resources/static/realtime.js @@ -46,6 +46,18 @@ function StartEpoch(timestamp) { {values: defaultData} ] }); + + if($('#messagesChart').length ) { + window.messagesChart = $('#messagesChart').epoch({ + type: 'time.area', + axes: ['bottom', 'left'], + height: 250, + data: [ + {values: defaultData}, + {values: defaultData} + ] + }); + } } function StartSSE(roomid) { @@ -63,6 +75,9 @@ function stats(e) { heapChart.push(data.heap) mallocsChart.push(data.mallocs) goroutinesChart.push(data.goroutines) + if(messagesChart) { + messagesChart.push(data.messages) + } } function parseJSONStats(e) { @@ -78,13 +93,18 @@ function parseJSONStats(e) { {time: timestamp, y: data.Mallocs}, {time: timestamp, y: data.Frees} ]; + var messages = [ + {time: timestamp, y: data.Inbound}, + {time: timestamp, y: data.Outbound} + ]; var goroutines = [ {time: timestamp, y: data.NuGoroutines}, ] return { heap: heap, mallocs: mallocs, - goroutines: goroutines + goroutines: goroutines, + messages: messages } } diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go index 0b9b8448..7c869ffa 100644 --- a/examples/realtime-advanced/stats.go +++ b/examples/realtime-advanced/stats.go @@ -14,12 +14,9 @@ func Stats() map[string]uint64 { "HeapInuse": stats.HeapInuse, "StackInuse": stats.StackInuse, "NuGoroutines": uint64(runtime.NumGoroutine()), - //"Latency": latency, - "Mallocs": stats.Mallocs, - "Frees": stats.Mallocs, - // "HeapIdle": stats.HeapIdle, - // "HeapInuse": stats.HeapInuse, - // "HeapReleased": stats.HeapReleased, - // "HeapObjects": stats.HeapObjects, + "Mallocs": stats.Mallocs, + "Frees": stats.Mallocs, + "Inbound": uint64(messages.Get("inbound")), + "Outbound": uint64(messages.Get("outbound")), } }