mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
update the online visitors and vuejs +iris mvc todo app (this gave me some ideas to make the api a bit easier)
Former-commit-id: 8c84486a22505b7137669bde52383d2564a6b382
This commit is contained in:
parent
55bdb44e26
commit
1c2472c53f
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
|
@ -8,6 +9,13 @@ import (
|
||||||
"github.com/kataras/iris/websocket"
|
"github.com/kataras/iris/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var events = websocket.Namespaces{
|
||||||
|
"default": websocket.Events{
|
||||||
|
websocket.OnRoomJoined: onRoomJoined,
|
||||||
|
websocket.OnRoomLeft: onRoomLeft,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// init the web application instance
|
// init the web application instance
|
||||||
// app := iris.New()
|
// app := iris.New()
|
||||||
|
@ -16,11 +24,9 @@ func main() {
|
||||||
// load templates
|
// load templates
|
||||||
app.RegisterView(iris.HTML("./templates", ".html").Reload(true))
|
app.RegisterView(iris.HTML("./templates", ".html").Reload(true))
|
||||||
// setup the websocket server
|
// setup the websocket server
|
||||||
ws := websocket.New(websocket.Config{})
|
ws := websocket.New(websocket.DefaultGorillaUpgrader, events)
|
||||||
ws.OnConnection(HandleWebsocketConnection)
|
|
||||||
|
|
||||||
app.Get("/my_endpoint", ws.Handler())
|
app.Get("/my_endpoint", websocket.Handler(ws))
|
||||||
app.Any("/iris-ws.js", websocket.ClientHandler())
|
|
||||||
|
|
||||||
// register static assets request path and system directory
|
// register static assets request path and system directory
|
||||||
app.HandleDir("/js", "./static/assets/js")
|
app.HandleDir("/js", "./static/assets/js")
|
||||||
|
@ -114,48 +120,54 @@ func (v *pageViews) Reset() {
|
||||||
|
|
||||||
var v pageViews
|
var v pageViews
|
||||||
|
|
||||||
// HandleWebsocketConnection handles the online viewers per example(gist source)
|
func viewsCountBytes(viewsCount uint64) []byte {
|
||||||
func HandleWebsocketConnection(c websocket.Connection) {
|
// * there are other methods to convert uint64 to []byte
|
||||||
|
return []byte(fmt.Sprintf("%d", viewsCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
func onRoomJoined(ns *websocket.NSConn, msg websocket.Message) error {
|
||||||
|
// the roomName here is the source.
|
||||||
|
pageSource := string(msg.Room)
|
||||||
|
|
||||||
c.On("watch", func(pageSource string) {
|
|
||||||
v.Add(pageSource)
|
v.Add(pageSource)
|
||||||
// join the socket to a room linked with the page source
|
|
||||||
c.Join(pageSource)
|
|
||||||
|
|
||||||
viewsCount := v.Get(pageSource).getCount()
|
viewsCount := v.Get(pageSource).getCount()
|
||||||
if viewsCount == 0 {
|
if viewsCount == 0 {
|
||||||
viewsCount++ // count should be always > 0 here
|
viewsCount++ // count should be always > 0 here
|
||||||
}
|
}
|
||||||
c.To(pageSource).Emit("watch", viewsCount)
|
|
||||||
|
// fire the "onNewVisit" client event
|
||||||
|
// on each connection joined to this room (source page)
|
||||||
|
// and notify of the new visit,
|
||||||
|
// including this connection (see nil on first input arg).
|
||||||
|
ns.Conn.Server().Broadcast(nil, websocket.Message{
|
||||||
|
Namespace: msg.Namespace,
|
||||||
|
Room: pageSource,
|
||||||
|
Event: "onNewVisit", // fire the "onNewVisit" client event.
|
||||||
|
Body: viewsCountBytes(viewsCount),
|
||||||
})
|
})
|
||||||
|
|
||||||
c.OnLeave(func(roomName string) {
|
return nil
|
||||||
if roomName != c.ID() { // if the roomName it's not the connection iself
|
}
|
||||||
// the roomName here is the source, this is the only room(except the connection's ID room) which we join the users to.
|
|
||||||
pageV := v.Get(roomName)
|
func onRoomLeft(ns *websocket.NSConn, msg websocket.Message) error {
|
||||||
|
// the roomName here is the source.
|
||||||
|
pageV := v.Get(msg.Room)
|
||||||
if pageV == nil {
|
if pageV == nil {
|
||||||
return // for any case that this room is not a pageView source
|
return nil // for any case that this room is not a pageView source
|
||||||
}
|
}
|
||||||
// decrement -1 the specific counter for this page source.
|
// decrement -1 the specific counter for this page source.
|
||||||
pageV.decrement()
|
pageV.decrement()
|
||||||
// 1. open 30 tabs.
|
|
||||||
// 2. close the browser.
|
|
||||||
// 3. re-open the browser
|
|
||||||
// 4. should be v.getCount() = 1
|
|
||||||
// in order to achieve the previous flow we should decrement exactly when the user disconnects
|
|
||||||
// but emit the result a little after, on a goroutine
|
|
||||||
// getting all connections within this room and emit the online views one by one.
|
|
||||||
// note:
|
|
||||||
// we can also add a time.Sleep(2-3 seconds) inside the goroutine at the future if we don't need 'real-time' updates.
|
|
||||||
go func(currentConnID string) {
|
|
||||||
for _, conn := range c.Server().GetConnectionsByRoom(roomName) {
|
|
||||||
if conn.ID() != currentConnID {
|
|
||||||
conn.Emit("watch", pageV.getCount())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}(c.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// fire the "onNewVisit" client event
|
||||||
|
// on each connection joined to this room (source page)
|
||||||
|
// and notify of the new, decremented by one, visits count.
|
||||||
|
ns.Conn.Server().Broadcast(nil, websocket.Message{
|
||||||
|
Namespace: msg.Namespace,
|
||||||
|
Room: msg.Room,
|
||||||
|
Event: "onNewVisit",
|
||||||
|
Body: viewsCountBytes(pageV.getCount()),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
(function() {
|
(function () {
|
||||||
var socket = new Ws("ws://localhost:8080/my_endpoint");
|
var events = {
|
||||||
|
default: {
|
||||||
socket.OnConnect(function () {
|
_OnNamespaceConnected: function (ns, msg) {
|
||||||
socket.Emit("watch", PAGE_SOURCE);
|
ns.joinRoom(PAGE_SOURCE);
|
||||||
});
|
},
|
||||||
|
_OnNamespaceDisconnect: function (ns, msg) {
|
||||||
|
document.getElementById("online_views").innerHTML = "you've been disconnected";
|
||||||
socket.On("watch", function (onlineViews) {
|
},
|
||||||
|
onNewVisit: function (ns, msg) {
|
||||||
var text = "1 online view";
|
var text = "1 online view";
|
||||||
|
var onlineViews = Number(msg.Body);
|
||||||
if (onlineViews > 1) {
|
if (onlineViews > 1) {
|
||||||
text = onlineViews + " online views";
|
text = onlineViews + " online views";
|
||||||
}
|
}
|
||||||
document.getElementById("online_views").innerHTML = text;
|
document.getElementById("online_views").innerHTML = text;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
socket.OnDisconnect(function () {
|
neffos.dial("ws://localhost:8080/my_endpoint", events).then(function (client) {
|
||||||
document.getElementById("online_views").innerHTML = "you've been disconnected";
|
client.connect("default");
|
||||||
});
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
var PAGE_SOURCE = {{ .PageID }}
|
var PAGE_SOURCE = {{ .PageID }}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/iris-ws.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/neffos.js@latest/dist/neffos.min.js"></script>
|
||||||
|
|
||||||
<script src="/js/visitors.js"></script>
|
<script src="/js/visitors.js"></script>
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
var PAGE_SOURCE = {{ .PageID }}
|
var PAGE_SOURCE = {{ .PageID }}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/iris-ws.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/neffos.js@latest/dist/neffos.min.js"></script>
|
||||||
|
|
||||||
<script src="/js/visitors.js"></script>
|
<script src="/js/visitors.js"></script>
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ type TodoController struct {
|
||||||
Service todo.Service
|
Service todo.Service
|
||||||
|
|
||||||
Session *sessions.Session
|
Session *sessions.Session
|
||||||
|
|
||||||
|
NS *websocket.NSConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeActivation called once before the server ran, and before
|
// BeforeActivation called once before the server ran, and before
|
||||||
|
@ -51,15 +53,14 @@ func (c *TodoController) Post(newItems []todo.Item) PostItemResponse {
|
||||||
return PostItemResponse{Success: true}
|
return PostItemResponse{Success: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TodoController) GetSync(conn websocket.Connection) {
|
func (c *TodoController) Save(msg websocket.Message) error {
|
||||||
// join to the session in order to send "saved"
|
id := c.Session.ID()
|
||||||
// events only to a single user, that means
|
c.NS.Conn.Server().Broadcast(nil, websocket.Message{
|
||||||
// that if user has opened more than one browser window/tab
|
Namespace: msg.Namespace,
|
||||||
// of the same session then the changes will be reflected to one another.
|
Event: "saved",
|
||||||
conn.Join(c.Session.ID())
|
To: id,
|
||||||
conn.On("save", func() { // "save" event from client.
|
Body: websocket.Marshal(c.Service.Get(id)),
|
||||||
conn.To(c.Session.ID()).Emit("saved", nil) // fire a "saved" event to the rest of the clients w.
|
|
||||||
})
|
})
|
||||||
|
|
||||||
conn.Wait()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
|
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
|
||||||
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/web/controllers"
|
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/web/controllers"
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
|
"github.com/kataras/iris/mvc"
|
||||||
"github.com/kataras/iris/sessions"
|
"github.com/kataras/iris/sessions"
|
||||||
"github.com/kataras/iris/websocket"
|
"github.com/kataras/iris/websocket"
|
||||||
|
|
||||||
"github.com/kataras/iris/mvc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -26,16 +27,8 @@ func main() {
|
||||||
Cookie: "iris_session",
|
Cookie: "iris_session",
|
||||||
})
|
})
|
||||||
|
|
||||||
// configure the websocket server.
|
// create a sub router and register the http controllers.
|
||||||
ws := websocket.New(websocket.Config{})
|
|
||||||
|
|
||||||
// create a sub router and register the client-side library for the iris websockets,
|
|
||||||
// you could skip it but iris websockets supports socket.io-like API.
|
|
||||||
todosRouter := app.Party("/todos")
|
todosRouter := app.Party("/todos")
|
||||||
// http://localhost:8080/todos/iris-ws.js
|
|
||||||
// serve the javascript client library to communicate with
|
|
||||||
// the iris high level websocket event system.
|
|
||||||
todosRouter.Any("/iris-ws.js", websocket.ClientHandler())
|
|
||||||
|
|
||||||
// create our mvc application targeted to /todos relative sub path.
|
// create our mvc application targeted to /todos relative sub path.
|
||||||
todosApp := mvc.New(todosRouter)
|
todosApp := mvc.New(todosRouter)
|
||||||
|
@ -44,11 +37,27 @@ func main() {
|
||||||
todosApp.Register(
|
todosApp.Register(
|
||||||
todo.NewMemoryService(),
|
todo.NewMemoryService(),
|
||||||
sess.Start,
|
sess.Start,
|
||||||
ws.Upgrade,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
todosController := new(controllers.TodoController)
|
||||||
// controllers registration here...
|
// controllers registration here...
|
||||||
todosApp.Handle(new(controllers.TodoController))
|
todosApp.Handle(todosController)
|
||||||
|
|
||||||
|
// Create a sub mvc app for websocket controller.
|
||||||
|
// Inherit the parent's dependencies.
|
||||||
|
todosWebsocketApp := todosApp.Party("/sync")
|
||||||
|
todosWebsocketApp.HandleWebsocket(todosController).
|
||||||
|
SetNamespace("todos").
|
||||||
|
SetEventMatcher(func(methodName string) (string, bool) {
|
||||||
|
return strings.ToLower(methodName), true
|
||||||
|
})
|
||||||
|
|
||||||
|
websocketServer := websocket.New(websocket.DefaultGorillaUpgrader, todosWebsocketApp)
|
||||||
|
idGenerator := func(ctx iris.Context) string {
|
||||||
|
id := sess.Start(ctx).ID()
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
todosWebsocketApp.Router.Get("/", websocket.Handler(websocketServer, idGenerator))
|
||||||
|
|
||||||
// start the web server at http://localhost:8080
|
// start the web server at http://localhost:8080
|
||||||
app.Run(iris.Addr(":8080"))
|
app.Run(iris.Addr(":8080"))
|
||||||
|
|
|
@ -11,8 +11,7 @@
|
||||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<script src="https://unpkg.com/director@1.2.8/build/director.js"></script>
|
<script src="https://unpkg.com/director@1.2.8/build/director.js"></script>
|
||||||
<!-- websocket sync between multiple tabs -->
|
<script src="https://cdn.jsdelivr.net/npm/neffos.js@latest/dist/neffos.min.js"></script>
|
||||||
<script src="/todos/iris-ws.js"></script>
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<style>
|
<style>
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
|
|
|
@ -1,15 +1,24 @@
|
||||||
// Full spec-compliant TodoMVC with Iris
|
// Full spec-compliant TodoMVC with Iris
|
||||||
// and hash-based routing in ~200 effective lines of JavaScript.
|
// and hash-based routing in ~200 effective lines of JavaScript.
|
||||||
|
|
||||||
var socket = new Ws("ws://localhost:8080/todos/sync");
|
var ws;
|
||||||
|
|
||||||
socket.On("saved", function () {
|
((async () => {
|
||||||
// console.log("receive: on saved");
|
const events = {
|
||||||
fetchTodos(function (items) {
|
todos: {
|
||||||
app.todos = items
|
saved: function (ns, msg) {
|
||||||
});
|
app.todos = msg.unmarshal()
|
||||||
});
|
// or make a new http fetch
|
||||||
|
// fetchTodos(function (items) {
|
||||||
|
// app.todos = msg.unmarshal()
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const conn = await neffos.dial("ws://localhost:8080/todos/sync", events);
|
||||||
|
ws = await conn.connect("todos");
|
||||||
|
})()).catch(console.error);
|
||||||
|
|
||||||
function fetchTodos(onComplete) {
|
function fetchTodos(onComplete) {
|
||||||
axios.get("/todos").then(response => {
|
axios.get("/todos").then(response => {
|
||||||
|
@ -38,7 +47,7 @@ var todoStorage = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// console.log("send: save");
|
// console.log("send: save");
|
||||||
socket.Emit("save")
|
ws.emit("save")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,4 +211,4 @@ window.addEventListener('hashchange', onHashChange)
|
||||||
onHashChange()
|
onHashChange()
|
||||||
|
|
||||||
// mount
|
// mount
|
||||||
app.$mount('.todoapp')
|
app.$mount('.todoapp');
|
39
mvc/mvc.go
39
mvc/mvc.go
|
@ -44,6 +44,7 @@ type Application struct {
|
||||||
Dependencies di.Values
|
Dependencies di.Values
|
||||||
Router router.Party
|
Router router.Party
|
||||||
Controllers []*ControllerActivator
|
Controllers []*ControllerActivator
|
||||||
|
websocketControllers []websocket.ConnHandler
|
||||||
ErrorHandler hero.ErrorHandler
|
ErrorHandler hero.ErrorHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,9 +186,23 @@ func (app *Application) Handle(controller interface{}) *Application {
|
||||||
// Note that a websocket controller is registered and ran under a specific connection connected to a namespace
|
// Note that a websocket controller is registered and ran under a specific connection connected to a namespace
|
||||||
// and it cannot send HTTP responses on that state.
|
// and it cannot send HTTP responses on that state.
|
||||||
// However all static and dynamic dependency injection features are working, as expected, like any regular MVC Controller.
|
// However all static and dynamic dependency injection features are working, as expected, like any regular MVC Controller.
|
||||||
func (app *Application) HandleWebsocket(controller interface{}) {
|
func (app *Application) HandleWebsocket(controller interface{}) *websocket.Struct {
|
||||||
c := app.handle(controller)
|
c := app.handle(controller)
|
||||||
c.markAsWebsocket()
|
c.markAsWebsocket()
|
||||||
|
|
||||||
|
websocketController := websocket.NewStruct(c.Value).SetInjector(makeInjector(c.injector))
|
||||||
|
app.websocketControllers = append(app.websocketControllers, websocketController)
|
||||||
|
return websocketController
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeInjector(injector *di.StructInjector) websocket.StructInjector {
|
||||||
|
return func(_ reflect.Type, nsConn *websocket.NSConn) reflect.Value {
|
||||||
|
v := injector.Acquire()
|
||||||
|
if injector.CanInject {
|
||||||
|
injector.InjectElem(v.Elem(), reflect.ValueOf(websocket.GetContext(nsConn.Conn)))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ websocket.ConnHandler = (*Application)(nil)
|
var _ websocket.ConnHandler = (*Application)(nil)
|
||||||
|
@ -200,27 +215,7 @@ func (app *Application) GetNamespaces() websocket.Namespaces {
|
||||||
websocket.EnableDebug(golog.Default)
|
websocket.EnableDebug(golog.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
makeInjector := func(injector *di.StructInjector) websocket.StructInjector {
|
return websocket.JoinConnHandlers(app.websocketControllers...).GetNamespaces()
|
||||||
return func(_ reflect.Type, nsConn *websocket.NSConn) reflect.Value {
|
|
||||||
v := injector.Acquire()
|
|
||||||
if injector.CanInject {
|
|
||||||
injector.InjectElem(v.Elem(), reflect.ValueOf(websocket.GetContext(nsConn.Conn)))
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var websocketControllers []websocket.ConnHandler
|
|
||||||
|
|
||||||
for _, c := range app.Controllers {
|
|
||||||
if c.servesWebsocket {
|
|
||||||
wsInjector := makeInjector(c.injector)
|
|
||||||
s := websocket.NewStruct(c.Value).SetInjector(wsInjector)
|
|
||||||
websocketControllers = append(websocketControllers, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return websocket.JoinConnHandlers(websocketControllers...).GetNamespaces()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) handle(controller interface{}) *ControllerActivator {
|
func (app *Application) handle(controller interface{}) *ControllerActivator {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user