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 (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/manucorporat/stats"
|
||||
|
@ -13,86 +11,31 @@ import (
|
|||
var messages = stats.New()
|
||||
|
||||
func main() {
|
||||
ConfigRuntime()
|
||||
StartWorkers()
|
||||
StartGin()
|
||||
}
|
||||
|
||||
func ConfigRuntime() {
|
||||
nuCPU := runtime.NumCPU()
|
||||
runtime.GOMAXPROCS(nuCPU)
|
||||
fmt.Printf("Running with %d CPUs\n", nuCPU)
|
||||
}
|
||||
|
||||
func StartWorkers() {
|
||||
go statsWorker()
|
||||
}
|
||||
|
||||
func StartGin() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
router := gin.New()
|
||||
router.Use(ratelimit, gin.Recovery(), gin.Logger())
|
||||
|
||||
router := gin.Default()
|
||||
router.LoadHTMLGlob("resources/*.templ.html")
|
||||
router.Static("/static", "resources/static")
|
||||
router.GET("/", index)
|
||||
router.GET("/room/:roomid", roomGET)
|
||||
router.POST("/room-post/:roomid", roomPOST)
|
||||
//router.DELETE("/room/:roomid", roomDELETE)
|
||||
router.GET("/stream/:roomid", streamRoom)
|
||||
|
||||
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 http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Login in Room "{{.roomid}}"</title>
|
||||
<title>Server-Sent Events. Room "{{.roomid}}"</title>
|
||||
<!-- jQuery -->
|
||||
<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>
|
||||
|
@ -32,6 +32,15 @@
|
|||
</style>
|
||||
</head>
|
||||
<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">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
|
@ -58,7 +67,7 @@
|
|||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<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>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
|
@ -95,7 +104,7 @@
|
|||
<legend>Join the SSE real-time chat</legend>
|
||||
<div class="form-group">
|
||||
<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 class="form-group text-center">
|
||||
<input type="submit" class="btn btn-success btn-login-submit" value="Join" />
|
||||
|
@ -129,7 +138,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<script src="/static/prismjs.min.js"></script>
|
||||
<h3>Server-side (Go)</h3>
|
||||
|
@ -160,6 +174,37 @@
|
|||
}</code></pre>
|
||||
</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>
|
||||
<footer>
|
||||
<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)
|
||||
}
|
||||
|
||||
func deleteBroadcast(roomid string) {
|
||||
b, ok := roomChannels[roomid]
|
||||
if ok {
|
||||
b.Close()
|
||||
delete(roomChannels, roomid)
|
||||
}
|
||||
}
|
||||
|
||||
func room(roomid string) broadcast.Broadcaster {
|
||||
b, ok := roomChannels[roomid]
|
||||
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,14 +2,21 @@ package main
|
|||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Stats() map[string]uint64 {
|
||||
var mutexStats sync.RWMutex
|
||||
var savedStats map[string]uint64
|
||||
|
||||
func statsWorker() {
|
||||
c := time.Tick(1 * time.Second)
|
||||
for range c {
|
||||
var stats runtime.MemStats
|
||||
runtime.ReadMemStats(&stats)
|
||||
|
||||
return map[string]uint64{
|
||||
mutexStats.Lock()
|
||||
savedStats = map[string]uint64{
|
||||
"timestamp": uint64(time.Now().Unix()),
|
||||
"HeapInuse": stats.HeapInuse,
|
||||
"StackInuse": stats.StackInuse,
|
||||
|
@ -19,4 +26,13 @@ func Stats() map[string]uint64 {
|
|||
"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