Updates realtime-advanced demo
This commit is contained in:
		@ -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 {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										73
									
								
								examples/realtime-advanced/routes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								examples/realtime-advanced/routes.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
@ -2,21 +2,37 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Stats() map[string]uint64 {
 | 
			
		||||
	var stats runtime.MemStats
 | 
			
		||||
	runtime.ReadMemStats(&stats)
 | 
			
		||||
var mutexStats sync.RWMutex
 | 
			
		||||
var savedStats map[string]uint64
 | 
			
		||||
 | 
			
		||||
	return map[string]uint64{
 | 
			
		||||
		"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")),
 | 
			
		||||
func statsWorker() {
 | 
			
		||||
	c := time.Tick(1 * time.Second)
 | 
			
		||||
	for range c {
 | 
			
		||||
		var stats runtime.MemStats
 | 
			
		||||
		runtime.ReadMemStats(&stats)
 | 
			
		||||
 | 
			
		||||
		mutexStats.Lock()
 | 
			
		||||
		savedStats = map[string]uint64{
 | 
			
		||||
			"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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user