forked from mirror/gin
Updates realtime-advanced demo
This commit is contained in:
parent
b0af2b4c11
commit
a8b9e2d8d6
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 "github.com/manucorporat/sse"
|
||||||
|
|
||||||
|
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: "message",
|
||||||
|
Data: "some data\nmore data",
|
||||||
|
})
|
||||||
|
|
||||||
|
// also a complex type, like a map, a struct or a slice
|
||||||
|
sse.Encode(w, sse.Event{
|
||||||
|
Id: "124",
|
||||||
|
Event: "message",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"user": "manu",
|
||||||
|
"date": time.Now().Unix(),
|
||||||
|
"content": "hi!",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}</code></pre>
|
||||||
|
<pre>event: message
|
||||||
|
data: some data\\nmore data
|
||||||
|
|
||||||
|
id: 124
|
||||||
|
event: message
|
||||||
|
data: {"content":"hi!","date":1431540810,"user":"manu"}</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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue