Updates realtime-advanced demo

This commit is contained in:
Manu Mtz-Almeida 2015-05-13 20:54:54 +02:00
parent b0af2b4c11
commit a8b9e2d8d6
6 changed files with 163 additions and 115 deletions

View File

@ -1,21 +0,0 @@
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/manucorporat/stats"
)
var ips = stats.New()
func ratelimit(c *gin.Context) {
ip := c.ClientIP()
value := uint64(ips.Add(ip, 1))
if value >= 1000 {
if value%1000 == 0 {
log.Printf("BlockedIP:%s Requests:%d\n", ip, value)
}
c.AbortWithStatus(401)
}
}

View File

@ -2,9 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"io"
"runtime" "runtime"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/manucorporat/stats" "github.com/manucorporat/stats"
@ -13,86 +11,31 @@ import (
var messages = stats.New() var messages = stats.New()
func main() { func main() {
ConfigRuntime()
StartWorkers()
StartGin()
}
func ConfigRuntime() {
nuCPU := runtime.NumCPU() nuCPU := runtime.NumCPU()
runtime.GOMAXPROCS(nuCPU) runtime.GOMAXPROCS(nuCPU)
fmt.Printf("Running with %d CPUs\n", nuCPU) fmt.Printf("Running with %d CPUs\n", nuCPU)
}
func StartWorkers() {
go statsWorker()
}
func StartGin() {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
router := gin.New() router := gin.Default()
router.Use(ratelimit, gin.Recovery(), gin.Logger())
router.LoadHTMLGlob("resources/*.templ.html") router.LoadHTMLGlob("resources/*.templ.html")
router.Static("/static", "resources/static") router.Static("/static", "resources/static")
router.GET("/", index) router.GET("/", index)
router.GET("/room/:roomid", roomGET) router.GET("/room/:roomid", roomGET)
router.POST("/room-post/:roomid", roomPOST) router.POST("/room-post/:roomid", roomPOST)
//router.DELETE("/room/:roomid", roomDELETE)
router.GET("/stream/:roomid", streamRoom) router.GET("/stream/:roomid", streamRoom)
router.Run("127.0.0.1:8080") router.Run("127.0.0.1:8080")
} }
func index(c *gin.Context) {
c.Redirect(301, "/room/hn")
}
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,
"timestamp": time.Now().Unix(),
})
}
func roomPOST(c *gin.Context) {
roomid := c.ParamValue("roomid")
nick := c.FormValue("nick")
message := c.PostFormValue("message")
if len(message) > 200 || len(nick) > 13 {
c.JSON(400, gin.H{
"status": "failed",
"error": "the message or nickname is too long",
})
return
}
post := gin.H{
"nick": nick,
"message": message,
}
messages.Add("inbound", 1)
room(roomid).Submit(post)
c.JSON(200, post)
}
func roomDELETE(c *gin.Context) {
roomid := c.ParamValue("roomid")
deleteBroadcast(roomid)
}
func streamRoom(c *gin.Context) {
roomid := c.ParamValue("roomid")
listener := openListener(roomid)
ticker := time.NewTicker(1 * time.Second)
defer closeListener(roomid, listener)
defer ticker.Stop()
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())
}
return true
})
}

View File

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Login in Room "{{.roomid}}"</title> <title>Server-Sent Events. Room "{{.roomid}}"</title>
<!-- jQuery --> <!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="http://malsup.github.com/jquery.form.js"></script> <script src="http://malsup.github.com/jquery.form.js"></script>
@ -32,6 +32,15 @@
</style> </style>
</head> </head>
<body> <body>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-62943585-1', 'auto');
ga('send', 'pageview');
</script>
<nav class="navbar navbar-fixed-top navbar-inverse"> <nav class="navbar navbar-fixed-top navbar-inverse">
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">
@ -58,7 +67,7 @@
<div class="jumbotron"> <div class="jumbotron">
<div class="container"> <div class="container">
<h1>Server-Sent Events in Go</h1> <h1>Server-Sent Events in Go</h1>
<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>Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. It is not websockets. <a href="http://www.html5rocks.com/en/tutorials/eventsource/basics/">Learn more.</a></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> <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"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
@ -95,7 +104,7 @@
<legend>Join the SSE real-time chat</legend> <legend>Join the SSE real-time chat</legend>
<div class="form-group"> <div class="form-group">
<label for="nick">Your Name</label> <label for="nick">Your Name</label>
<input value='' name="nick" id="nick" placeholder="Your name" type="text" class="form-control" /> <input value='' name="nick" id="nick" placeholder="John" type="text" class="form-control" />
</div> </div>
<div class="form-group text-center"> <div class="form-group text-center">
<input type="submit" class="btn btn-success btn-login-submit" value="Join" /> <input type="submit" class="btn btn-success btn-login-submit" value="Join" />
@ -129,7 +138,12 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h2>Source code</h2> <h2>MIT Open Sourced</h2>
<ul>
<li><a href="https://github.com/gin-gonic/gin/tree/develop/examples/realtime-advanced">This demo website (JS and Go)</a></li>
<li><a href="https://github.com/manucorporat/sse">The SSE implementation in Go</a></li>
<li><a href="https://github.com/gin-gonic/gin">The Web Framework (Gin)</a></li>
</ul>
<div class="col-md-6"> <div class="col-md-6">
<script src="/static/prismjs.min.js"></script> <script src="/static/prismjs.min.js"></script>
<h3>Server-side (Go)</h3> <h3>Server-side (Go)</h3>
@ -160,6 +174,37 @@
}</code></pre> }</code></pre>
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-12">
<h3>SSE package</h3>
<pre><code class="language-go">import &quot;github.com/manucorporat/sse&quot;
func httpHandler(w http.ResponseWriter, req *http.Request) {
// data can be a primitive like a string, an integer or a float
sse.Encode(w, sse.Event{
Event: &quot;message&quot;,
Data: &quot;some data\nmore data&quot;,
})
// also a complex type, like a map, a struct or a slice
sse.Encode(w, sse.Event{
Id: &quot;124&quot;,
Event: &quot;message&quot;,
Data: map[string]interface{}{
&quot;user&quot;: &quot;manu&quot;,
&quot;date&quot;: time.Now().Unix(),
&quot;content&quot;: &quot;hi!&quot;,
},
})
}</code></pre>
<pre>event: message
data: some data\\nmore data
id: 124
event: message
data: {&quot;content&quot;:&quot;hi!&quot;,&quot;date&quot;:1431540810,&quot;user&quot;:&quot;manu&quot;}</pre>
</div>
</div>
<hr> <hr>
<footer> <footer>
<p>Created with <span class="glyphicon glyphicon-heart"></span> by <a href="https://github.com/manucorporat">Manu Martinez-Almeida</a></p> <p>Created with <span class="glyphicon glyphicon-heart"></span> by <a href="https://github.com/manucorporat">Manu Martinez-Almeida</a></p>

View File

@ -15,14 +15,6 @@ func closeListener(roomid string, listener chan interface{}) {
close(listener) close(listener)
} }
func deleteBroadcast(roomid string) {
b, ok := roomChannels[roomid]
if ok {
b.Close()
delete(roomChannels, roomid)
}
}
func room(roomid string) broadcast.Broadcaster { func room(roomid string) broadcast.Broadcaster {
b, ok := roomChannels[roomid] b, ok := roomChannels[roomid]
if !ok { if !ok {

View File

@ -0,0 +1,73 @@
package main
import (
"html"
"io"
"time"
"github.com/gin-gonic/gin"
)
func index(c *gin.Context) {
c.Redirect(301, "/room/hn")
}
func roomGET(c *gin.Context) {
roomid := c.ParamValue("roomid")
nick := c.FormValue("nick")
if len(nick) < 2 {
nick = ""
}
if len(nick) > 13 {
nick = nick[0:12] + "..."
}
c.HTML(200, "room_login.templ.html", gin.H{
"roomid": roomid,
"nick": nick,
"timestamp": time.Now().Unix(),
})
}
func roomPOST(c *gin.Context) {
roomid := c.ParamValue("roomid")
nick := c.FormValue("nick")
message := c.PostFormValue("message")
validMessage := len(message) > 1 && len(message) < 200
validNick := len(nick) > 1 && len(nick) < 14
if !validMessage || !validNick {
c.JSON(400, gin.H{
"status": "failed",
"error": "the message or nickname is too long",
})
return
}
post := gin.H{
"nick": html.EscapeString(nick),
"message": html.EscapeString(message),
}
messages.Add("inbound", 1)
room(roomid).Submit(post)
c.JSON(200, post)
}
func streamRoom(c *gin.Context) {
roomid := c.ParamValue("roomid")
listener := openListener(roomid)
ticker := time.NewTicker(1 * time.Second)
defer closeListener(roomid, listener)
defer ticker.Stop()
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())
}
return true
})
}

View File

@ -2,21 +2,37 @@ package main
import ( import (
"runtime" "runtime"
"sync"
"time" "time"
) )
func Stats() map[string]uint64 { var mutexStats sync.RWMutex
var stats runtime.MemStats var savedStats map[string]uint64
runtime.ReadMemStats(&stats)
return map[string]uint64{ func statsWorker() {
"timestamp": uint64(time.Now().Unix()), c := time.Tick(1 * time.Second)
"HeapInuse": stats.HeapInuse, for range c {
"StackInuse": stats.StackInuse, var stats runtime.MemStats
"NuGoroutines": uint64(runtime.NumGoroutine()), runtime.ReadMemStats(&stats)
"Mallocs": stats.Mallocs,
"Frees": stats.Mallocs, mutexStats.Lock()
"Inbound": uint64(messages.Get("inbound")), savedStats = map[string]uint64{
"Outbound": uint64(messages.Get("outbound")), "timestamp": uint64(time.Now().Unix()),
"HeapInuse": stats.HeapInuse,
"StackInuse": stats.StackInuse,
"NuGoroutines": uint64(runtime.NumGoroutine()),
"Mallocs": stats.Mallocs,
"Frees": stats.Mallocs,
"Inbound": uint64(messages.Get("inbound")),
"Outbound": uint64(messages.Get("outbound")),
}
mutexStats.Unlock()
} }
} }
func Stats() map[string]uint64 {
mutexStats.RLock()
defer mutexStats.RUnlock()
return savedStats
}