Updates realtime-advanced demo

This commit is contained in:
Manu Mtz-Almeida 2015-05-13 16:44:44 +02:00
parent 9386d78673
commit 1f11541011
7 changed files with 263 additions and 25 deletions

View File

@ -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)

View File

@ -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())

View File

@ -19,13 +19,41 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap-theme.min.css">
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<!-- Primjs -->
<link href="/static/prismjs.min.css" rel="stylesheet" />
<script type="text/javascript">
$(document).ready(function() {
StartRealtime({{.roomid}}, {{.timestamp}});
});
</script>
<style>
body { padding-top: 50px; }
</style>
</head>
<body>
<nav class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Server-Sent Events</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Demo</a></li>
<li><a href="http://www.w3.org/TR/2009/WD-eventsource-20091029/">W3 Standard</a></li>
<li><a href="http://caniuse.com/#feat=eventsource">Browser Support</a></li>
<li><a href="http://gin-gonic.github.io/gin/">Gin Framework</a></li>
<li><a href="https://github.com/gin-gonic/gin/tree/develop/examples/realtime-advanced">Github</a></li>
</ul>
</div><!-- /.nav-collapse -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<div class="container">
@ -33,10 +61,8 @@
<p><a href="http://www.html5rocks.com/en/tutorials/eventsource/basics/">Server-sent events (SSE)</a> is a technology where a browser receives automatic updates from a server via HTTP connection.</p>
<p>The chat and the charts data is provided in realtime using the SSE implemention of <a href="https://github.com/gin-gonic/gin/blob/15b0c49da556d58a3d934b86e3aa552ff224026d/examples/realtime-chat/main.go#L23-L32">Gin Framework</a>.</p>
<div class="row">
{{if not .nick}}
<div class="col-md-8">
{{end}}
<div id="chat-scroll" style="overflow-y:scroll; overflow-x:scroll; height:200px">
<div id="chat-scroll" style="overflow-y:scroll; overflow-x:scroll; height:270px">
<table id="table-style" class="table">
<thead>
<tr>
@ -49,17 +75,22 @@
</div>
{{if .nick}}
<form class="form-inline" id="chat-form" action="/room/{{.roomid}}?nick={{.nick}}" method="post">
<div class="form-group col-md-6">
<input name="message" id="chat-message" placeholder="a message" type="text" class="form-control" style="width:100%" />
</div>
<div class="form-group col-md-2">
<input type="submit" class="btn btn-primary" value="Send" />
<div class="form-group">
<label class="sr-only" for="chat-message">Message</label>
<div class="input-group">
<div class="input-group-addon">{{.nick}}</div>
<input name="message" id="chat-message" placeholder="a message" type="text" class="form-control" />
</div>
</div>
<input type="submit" class="btn btn-primary" value="Send" />
</form>
{{end}}
{{if not .nick}}
</div>
<div class="col-md-4">
{{if .nick}}
<h3>Inbound/Outbound</h3>
<div id="messagesChart" class="epoch category20c"></div>
{{else}}
<form action="" method="get">
<legend>Join the SSE real-time chat</legend>
<div class="form-group">
@ -70,13 +101,14 @@
<input type="submit" class="btn btn-success btn-login-submit" value="Join" />
</div>
</form>
{{end}}
</div>
{{end}}
</div>
</div>
</div>
<div class="container">
<div class="row">
<h2>Realtime server Go stats</h2>
<div class="col-md-4">
<h3>Number of Goroutines</h3>
<p>
@ -96,9 +128,41 @@
</p>
</div>
</div>
<div class="row">
<h2>Source code</h2>
<div class="col-md-6">
<script src="/static/prismjs.min.js"></script>
<h3>Server-side (Go)</h3>
<pre><code class="language-go">func streamRoom(c *gin.Context) {
roomid := c.ParamValue(&quot;roomid&quot;)
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 := &lt;-listener:
c.SSEvent(&quot;message&quot;, msg)
case &lt;-statsTicker.C:
c.SSEvent(&quot;stats&quot;, Stats())
}
return true
})
}</code></pre>
</div>
<div class="col-md-6">
<h3>Client-side (JS)</h3>
<pre><code class="language-javascript">function StartSSE(roomid) {
var source = new EventSource('/stream/'+roomid);
source.addEventListener('message', newChatMessage, false);
source.addEventListener('stats', stats, false);
}</code></pre>
</div>
</div>
<hr>
<footer>
<p>&copy; Company 2014</p>
<p>Created with <span class="glyphicon glyphicon-heart"></span> by <a href="https://github.com/manucorporat">Manu Martinez-Almeida</a></p>
</footer>
</div>
</body>

View File

@ -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;
}

View File

@ -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,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=t.util.clone(e[r]));return a;case"Array":return e.map(function(e){return t.util.clone(e)})}return e}},languages:{extend:function(e,n){var a=t.util.clone(t.languages[e]);for(var r in n)a[r]=n[r];return a},insertBefore:function(e,n,a,r){r=r||t.languages;var i=r[e];if(2==arguments.length){a=arguments[1];for(var l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);return i}var s={};for(var o in i)if(i.hasOwnProperty(o)){if(o==n)for(var l in a)a.hasOwnProperty(l)&&(s[l]=a[l]);s[o]=i[o]}return t.languages.DFS(t.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=s)}),r[e]=s},DFS:function(e,n,a){for(var r in e)e.hasOwnProperty(r)&&(n.call(e,r,e[r],a||r),"Object"===t.util.type(e[r])?t.languages.DFS(e[r],n):"Array"===t.util.type(e[r])&&t.languages.DFS(e[r],n,r))}},highlightAll:function(e,n){for(var a,r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'),i=0;a=r[i++];)t.highlightElement(a,e===!0,n)},highlightElement:function(a,r,i){for(var l,s,o=a;o&&!e.test(o.className);)o=o.parentNode;if(o&&(l=(o.className.match(e)||[,""])[1],s=t.languages[l]),s){a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,o=a.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var u=a.textContent;if(u){u=u.replace(/^(?:\r?\n|\r)/,"");var g={element:a,language:l,grammar:s,code:u};if(t.hooks.run("before-highlight",g),r&&self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){g.highlightedCode=n.stringify(JSON.parse(e.data),l),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(g.element),t.hooks.run("after-highlight",g)},c.postMessage(JSON.stringify({language:g.language,code:g.code}))}else g.highlightedCode=t.highlight(g.code,g.grammar,g.language),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(a),t.hooks.run("after-highlight",g)}}},highlight:function(e,a,r){var i=t.tokenize(e,a);return n.stringify(t.util.encode(i),r)},tokenize:function(e,n){var a=t.Token,r=[e],i=n.rest;if(i){for(var l in i)n[l]=i[l];delete n.rest}e:for(var l in n)if(n.hasOwnProperty(l)&&n[l]){var s=n[l];s="Array"===t.util.type(s)?s:[s];for(var o=0;o<s.length;++o){var u=s[o],g=u.inside,c=!!u.lookbehind,f=0,h=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var d=r[p];if(r.length>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+"</"+i.tag+">"},!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:/<script[\w\W]*?>[\w\W]*?<\/script>/i,inside:{tag:{pattern:/<script[\w\W]*?>|<\/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"];;

View File

@ -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
}
}

View File

@ -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")),
}
}