=sse: events

This commit is contained in:
Miki 2023-12-20 13:06:41 +01:00
parent f4462e5df1
commit 1f96ed0930
28 changed files with 209 additions and 138 deletions

1
sse/be/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
static

BIN
sse/be/be

Binary file not shown.

View file

@ -1,9 +1,12 @@
package main
import (
"encoding/json"
"crypto/sha256"
"fmt"
"io"
"log"
"strconv"
"strings"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
@ -15,14 +18,20 @@ type Message struct {
Id string `json:"id"`
}
type Event struct {
Message chan Message
NewClient chan chan Message
ClosedClient chan chan Message
TotalClients map[chan Message]bool
type ClientChan chan Message
type Client struct {
Id string `json:"id"`
Ip string `json:"ip"`
Chan ClientChan `json:"chan"`
Events []string `json:"events"`
}
type ClientChan chan Message
type Event struct {
Clients []Client `json:"clients"`
Id int `json:"id"`
Events []string `json:"events"`
}
func main() {
@ -32,10 +41,12 @@ func main() {
event := NewServer()
api := r.Group("/api")
api.GET("/sse", HeadersMiddleware(), event.serveHTTP(), event.retvalSSE())
api.GET("/msg", event.SendNewMsg())
api.GET("/ping", event.SendPing())
api.GET("/pong", event.SendPong())
// api.GET("/sse", HeadersMiddleware(), event.serveHTTP(), event.retvalSSE())
api.GET("/sse", HeadersMiddleware(), event.Stream)
api.GET("/msg", event.SendMsg)
api.GET("/message", event.SendMessage)
api.GET("/ping", event.SendPing)
api.GET("/pong", event.SendPong)
sse := r.Group("/sse")
sse.Static("/", "./static/sse")
@ -56,16 +67,16 @@ func main() {
}
func NewServer() *Event {
event := &Event{
Message: make(chan Message),
NewClient: make(chan chan string),
ClosedClient: make(chan chan string),
TotalClients: make(map[chan string]bool),
return &Event{
Clients: []Client{},
Id: 0,
Events: []string{
"ping",
"pong",
"msg",
"message",
},
}
go event.Broadcast()
return event
}
func HeadersMiddleware() gin.HandlerFunc {
@ -79,98 +90,146 @@ func HeadersMiddleware() gin.HandlerFunc {
}
}
func (e *Event) serveHTTP() gin.HandlerFunc {
return func(c *gin.Context) {
// Initialize client channel
clientChan := make(ClientChan)
func (e *Event) Stream(c *gin.Context) {
client := &Client{
Ip: c.ClientIP(),
Chan: make(ClientChan),
Events: c.QueryArray("events"),
}
// Send new connection to event server
e.NewClient <- clientChan
client.Id = Id(client.Ip, client.Events, 8)
defer func() {
// Send closed connection to event server
e.ClosedClient <- clientChan
}()
log.Printf("Client: %+v", client)
c.Set("clientChan", clientChan)
e.Clients = append(e.Clients, *client)
c.Next()
defer func() {
e.Clients = remove(e.Clients, client)
close(client.Chan)
log.Printf("Client %s disconnected - tot: %d", client.Ip, len(e.Clients))
}()
log.Printf("Client %s connected - tot: %d", client.Ip, len(e.Clients))
c.Stream(func(w io.Writer) bool {
msg, ok := <-client.Chan
if ok {
c.SSEvent(msg.Event, msg)
}
return ok
})
}
func (e *Event) Send(msg Message) {
e.Id++
msg.Id = strconv.Itoa(e.Id)
for _, client := range e.Clients {
client.Chan <- msg
}
}
func (e *Event) retvalSSE() gin.HandlerFunc {
return func(c *gin.Context) {
v, ok := c.Get("clientChan")
if !ok {
return
}
clientChan, ok := v.(ClientChan)
if !ok {
return
}
c.Stream(func(w io.Writer) bool {
msg, ok := <-clientChan
if ok {
c.SSEvent("message", msg)
}
return ok
})
}
func (e *Event) SendMessage(c *gin.Context) {
e.Send(Message{
Event: "message",
Data: "Ciao",
})
}
func (e *Event) Broadcast() {
id := 0
for {
select {
case client := <-e.NewClient:
e.TotalClients[client] = true
case client := <-e.ClosedClient:
delete(e.TotalClients, client)
close(client)
case message := <-e.Message:
id++
message.Id = strconv.Itoa(id)
for client := range e.TotalClients {
// msg, _ := json.Marshal(message)
// client <- string(msg)
client <- message
}
func (e *Event) SendMsg(c *gin.Context) {
e.Send(Message{
Event: "msg",
Data: "hello",
})
}
func (e *Event) SendPing(c *gin.Context) {
e.Send(Message{
Event: "ping",
Data: "Ping",
})
}
func (e *Event) SendPong(c *gin.Context) {
e.Send(Message{
Event: "pong",
Data: "Pong",
})
}
func remove(slice []Client, s *Client) []Client {
for i, v := range slice {
if v.Id == s.Id {
return append(slice[:i], slice[i+1:]...)
}
}
return slice
}
func (e *Event) SendNewMsg() gin.HandlerFunc {
return func(c *gin.Context) {
e.SendMsg(Message{
Event: "message",
Data: "hello",
Id: "1",
})
}
func Id(ip string, events []string, len int) string {
str := ip + strings.Join(events, "|")
id := sha256.Sum256([]byte(str))
return fmt.Sprintf("%X", id)[0:len]
}
func (e *Event) SendPing() gin.HandlerFunc {
return func(c *gin.Context) {
e.SendMsg(Message{
Event: "ping",
Data: "hello",
Id: "1",
})
}
}
// func (e *Event) serveHTTP() gin.HandlerFunc {
// return func(c *gin.Context) {
// // Initialize client channel
// clientChan := make(ClientChan)
func (e *Event) SendPong() gin.HandlerFunc {
return func(c *gin.Context) {
e.SendMsg(Message{
Event: "pong",
Data: "hello",
Id: "1",
})
}
}
// // Send new connection to event server
// e.NewClient <- clientChan
func (e *Event) SendMsg(msg Message) {
e.Message <- msg
}
// defer func() {
// // Send closed connection to event server
// e.ClosedClient <- clientChan
// }()
// c.Set("clientChan", clientChan)
// c.Next()
// }
// }
// func (e *Event) retvalSSE() gin.HandlerFunc {
// return func(c *gin.Context) {
// v, ok := c.Get("clientChan")
// if !ok {
// return
// }
// clientChan, ok := v.(ClientChan)
// if !ok {
// return
// }
// c.Stream(func(w io.Writer) bool {
// msg, ok := <-clientChan
// if ok {
// c.SSEvent("message", msg)
// }
// return ok
// })
// }
// }
// func (e *Event) Broadcast() {
// id := 0
// for {
// select {
// case client := <-e.NewClient:
// e.TotalClients[client] = true
// case client := <-e.ClosedClient:
// delete(e.TotalClients, client)
// close(client)
// case message := <-e.Message:
// id++
// for client := range e.TotalClients {
// // msg, _ := json.Marshal(message)
// // client <- string(msg)
// client <- message
// }
// }
// }
// }

View file

@ -1 +0,0 @@
import{n as d,s as k}from"./scheduler.k-kUyWhY.js";const u=[];function p(t,e=d){let n;const o=new Set;function a(s){if(k(t,s)&&(t=s,n)){const c=!u.length;for(const i of o)i[1](),u.push(i,t);if(c){for(let i=0;i<u.length;i+=2)u[i][0](u[i+1]);u.length=0}}}function l(s){a(s(t))}function r(s,c=d){const i=[s,c];return o.add(i),o.size===1&&(n=e(a,l)||d),s(t),()=>{o.delete(i),o.size===0&&n&&(n(),n=null)}}return{set:a,update:l,subscribe:r}}const m=globalThis.__sveltekit_1omqn18?.base??"",E=globalThis.__sveltekit_1omqn18?.assets??m,A="1703001309124",I="sveltekit:snapshot",w="sveltekit:scroll",y="sveltekit:states",N="sveltekit:pageurl",U="sveltekit:history",L="sveltekit:navigation",_={tap:1,hover:2,viewport:3,eager:4,off:-1,false:-1},g=location.origin;function O(t){if(t instanceof URL)return t;let e=document.baseURI;if(!e){const n=document.getElementsByTagName("base");e=n.length?n[0].href:document.URL}return new URL(t,e)}function Y(){return{x:pageXOffset,y:pageYOffset}}function f(t,e){return t.getAttribute(`data-sveltekit-${e}`)}const b={..._,"":_.hover};function v(t){let e=t.assignedSlot??t.parentNode;return e?.nodeType===11&&(e=e.host),e}function q(t,e){for(;t&&t!==e;){if(t.nodeName.toUpperCase()==="A"&&t.hasAttribute("href"))return t;t=v(t)}}function x(t,e){let n;try{n=new URL(t instanceof SVGAElement?t.href.baseVal:t.href,document.baseURI)}catch{}const o=t instanceof SVGAElement?t.target.baseVal:t.target,a=!n||!!o||T(n,e)||(t.getAttribute("rel")||"").split(/\s+/).includes("external"),l=n?.origin===g&&t.hasAttribute("download");return{url:n,external:a,target:o,download:l}}function P(t){let e=null,n=null,o=null,a=null,l=null,r=null,s=t;for(;s&&s!==document.documentElement;)o===null&&(o=f(s,"preload-code")),a===null&&(a=f(s,"preload-data")),e===null&&(e=f(s,"keepfocus")),n===null&&(n=f(s,"noscroll")),l===null&&(l=f(s,"reload")),r===null&&(r=f(s,"replacestate")),s=v(s);function c(i){switch(i){case"":case"true":return!0;case"off":case"false":return!1;default:return}}return{preload_code:b[o??"off"],preload_data:b[a??"off"],keepfocus:c(e),noscroll:c(n),reload:c(l),replace_state:c(r)}}function h(t){const e=p(t);let n=!0;function o(){n=!0,e.update(r=>r)}function a(r){n=!1,e.set(r)}function l(r){let s;return e.subscribe(c=>{(s===void 0||n&&c!==s)&&r(s=c)})}return{notify:o,set:a,subscribe:l}}function R(){const{set:t,subscribe:e}=p(!1);let n;async function o(){clearTimeout(n);try{const a=await fetch(`${E}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!a.ok)return!1;const r=(await a.json()).version!==A;return r&&(t(!0),clearTimeout(n)),r}catch{return!1}}return{subscribe:e,check:o}}function T(t,e){return t.origin!==g||!t.pathname.startsWith(e)}function V(t){t.client}const G={url:h({}),page:h({}),navigating:p(null),updated:R()};export{U as H,L as N,N as P,w as S,y as a,I as b,P as c,G as d,m as e,q as f,x as g,_ as h,T as i,V as j,g as o,O as r,Y as s};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
import{s as E,n as b,e as x}from"../chunks/scheduler.k-kUyWhY.js";import{S,i as j,g as _,m as f,s as q,h as d,j as g,n as h,f as p,c as y,a as l,x as v,o as $}from"../chunks/index.PctGSwCt.js";import{d as C}from"../chunks/singletons.4aHkxUVP.js";const H=()=>{const s=C;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},P={subscribe(s){return H().page.subscribe(s)}};function k(s){let t,r=s[0].status+"",o,n,i,c=s[0].error?.message+"",u;return{c(){t=_("h1"),o=f(r),n=q(),i=_("p"),u=f(c)},l(e){t=d(e,"H1",{});var a=g(t);o=h(a,r),a.forEach(p),n=y(e),i=d(e,"P",{});var m=g(i);u=h(m,c),m.forEach(p)},m(e,a){l(e,t,a),v(t,o),l(e,n,a),l(e,i,a),v(i,u)},p(e,[a]){a&1&&r!==(r=e[0].status+"")&&$(o,r),a&1&&c!==(c=e[0].error?.message+"")&&$(u,c)},i:b,o:b,d(e){e&&(p(t),p(n),p(i))}}}function w(s,t,r){let o;return x(s,P,n=>r(0,o=n)),[o]}let D=class extends S{constructor(t){super(),j(this,t,w,k,E,{})}};export{D as component};

View file

@ -1 +0,0 @@
import{s as N,n as y,r as O,o as D}from"../chunks/scheduler.k-kUyWhY.js";import{S as E,i as T,g as m,m as k,s as x,h as w,j as z,n as B,f as u,c as b,y as C,a as g,x as J,z as h,o as L}from"../chunks/index.PctGSwCt.js";function M(p){let c,s,n,i,o="send Msg",t,l,S="send Ping",d,r,v="send Pong",_,$;return{c(){c=m("h1"),s=k(p[0]),n=x(),i=m("button"),i.textContent=o,t=x(),l=m("button"),l.textContent=S,d=x(),r=m("button"),r.textContent=v},l(e){c=w(e,"H1",{});var a=z(c);s=B(a,p[0]),a.forEach(u),n=b(e),i=w(e,"BUTTON",{"data-svelte-h":!0}),C(i)!=="svelte-1a1zt14"&&(i.textContent=o),t=b(e),l=w(e,"BUTTON",{"data-svelte-h":!0}),C(l)!=="svelte-iklzn2"&&(l.textContent=S),d=b(e),r=w(e,"BUTTON",{"data-svelte-h":!0}),C(r)!=="svelte-1i1hqj6"&&(r.textContent=v)},m(e,a){g(e,c,a),J(c,s),g(e,n,a),g(e,i,a),g(e,t,a),g(e,l,a),g(e,d,a),g(e,r,a),_||($=[h(i,"click",P),h(l,"click",U),h(r,"click",j)],_=!0)},p(e,[a]){a&1&&L(s,e[0])},i:y,o:y,d(e){e&&(u(c),u(n),u(i),u(t),u(l),u(d),u(r)),_=!1,O($)}}}function f(){return new Date(Date.now()).toISOString()}async function P(){await fetch("/api/msg")}async function U(){await fetch("/api/ping")}async function j(){await fetch("/api/pong")}function q(p,c,s){let n;function i(){const o=new EventSource("/api/sse");return console.log("subscribe"),o.onopen=()=>{console.log(`sse open ${f()}`),s(0,n=`sse open ${f()}`)},o.onerror=()=>{console.log(`sse error ${f()}`),o.readyState===EventSource.CONNECTING&&console.log(`sse reconnecting ${f()}`),s(0,n=`sse error ${f()}`)},o.onmessage=t=>{let l=JSON.parse(t.data);console.log(`received: ${JSON.stringify(l)}`),s(0,n=`received: ${JSON.stringify(l)} ${Date.now()}`)},o.addEventListener("ping",t=>{console.log(`ping: ${t.data}`),s(0,n=`ping: ${t.data} ${Date.now()}`)}),o.addEventListener("pong",t=>{console.log(`pong: ${t.data}`),s(0,n=`pong: ${t.data} ${Date.now()}`)}),o.addEventListener("message",t=>{console.log(`message: ${t.data}`),s(0,n=`message: ${t.data} ${Date.now()}`)}),()=>{o.close(),console.log(`sse closing ${Date.now()}`)}}return D(()=>i()),s(0,n=""),[n]}class H extends E{constructor(c){super(),T(this,c,q,M,N,{})}}export{H as component};

View file

@ -1 +1 @@
{"version":"1703001309124"}
{"version":"1703070251152"}

Binary file not shown.

Binary file not shown.

View file

@ -5,17 +5,17 @@
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="modulepreload" href="/_app/immutable/entry/start.PsjcEp4p.js">
<link rel="modulepreload" href="/_app/immutable/entry/start.tfT6_LLF.js">
<link rel="modulepreload" href="/_app/immutable/chunks/scheduler.k-kUyWhY.js">
<link rel="modulepreload" href="/_app/immutable/chunks/singletons.4aHkxUVP.js">
<link rel="modulepreload" href="/_app/immutable/entry/app.zLHOYlGu.js">
<link rel="modulepreload" href="/_app/immutable/chunks/singletons.y0J0ZVZx.js">
<link rel="modulepreload" href="/_app/immutable/entry/app.ck5GlgzY.js">
<link rel="modulepreload" href="/_app/immutable/chunks/index.PctGSwCt.js">
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">
<script>
{
__sveltekit_1omqn18 = {
__sveltekit_lfjj3r = {
base: "",
env: null
};
@ -23,8 +23,8 @@
const element = document.currentScript.parentElement;
Promise.all([
import("/_app/immutable/entry/start.PsjcEp4p.js"),
import("/_app/immutable/entry/app.zLHOYlGu.js")
import("/_app/immutable/entry/start.tfT6_LLF.js"),
import("/_app/immutable/entry/app.ck5GlgzY.js")
]).then(([kit, app]) => {
kit.start(app, element);
});

Binary file not shown.

Binary file not shown.

View file

@ -5,17 +5,17 @@
<link rel="icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="modulepreload" href="../_app/immutable/entry/start.PsjcEp4p.js">
<link rel="modulepreload" href="../_app/immutable/entry/start.tfT6_LLF.js">
<link rel="modulepreload" href="../_app/immutable/chunks/scheduler.k-kUyWhY.js">
<link rel="modulepreload" href="../_app/immutable/chunks/singletons.4aHkxUVP.js">
<link rel="modulepreload" href="../_app/immutable/entry/app.zLHOYlGu.js">
<link rel="modulepreload" href="../_app/immutable/chunks/singletons.y0J0ZVZx.js">
<link rel="modulepreload" href="../_app/immutable/entry/app.ck5GlgzY.js">
<link rel="modulepreload" href="../_app/immutable/chunks/index.PctGSwCt.js">
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">
<script>
{
__sveltekit_1omqn18 = {
__sveltekit_lfjj3r = {
base: new URL("..", location).pathname.slice(0, -1),
env: null
};
@ -23,8 +23,8 @@
const element = document.currentScript.parentElement;
Promise.all([
import("../_app/immutable/entry/start.PsjcEp4p.js"),
import("../_app/immutable/entry/app.zLHOYlGu.js")
import("../_app/immutable/entry/start.tfT6_LLF.js"),
import("../_app/immutable/entry/app.ck5GlgzY.js")
]).then(([kit, app]) => {
kit.start(app, element);
});

Binary file not shown.

Binary file not shown.

View file

@ -2,6 +2,9 @@
import { onMount } from'svelte';
$: msg = "";
$: jmsg = {};
$: dping = "";
$: dpong = "";
function now() {
const now = new Date(Date.now());
@ -22,29 +25,37 @@
console.log(`sse error ${now()}`);
if (sse.readyState === EventSource.CONNECTING) {
console.log(`sse reconnecting ${now()}`);
msg = `sse reconnecting ${now()}`;
} else {
msg = `sse error ${now()}`;
}
msg = `sse error ${now()}`;
};
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
msg = `received: ${JSON.stringify(Msg)} ${Date.now()}`;
msg = `received: ${JSON.stringify(Msg)} ${now()}`;
jmsg = Msg;
};
sse.addEventListener('ping', (e) => {
console.log(`ping: ${e.data}`);
msg = `ping: ${e.data} ${Date.now()}`;
let Msg = JSON.parse(e.data);
msg = `ping: ${Msg.id} ${Msg.data} ${now()}`;
console.log(msg);
dping = Msg.id;
});
sse.addEventListener('pong', (e) => {
console.log(`pong: ${e.data}`);
msg = `pong: ${e.data} ${Date.now()}`;
let Msg = JSON.parse(e.data);
msg = `pong: ${Msg.id} ${Msg.data} ${now()}`;
console.log(msg);
dpong = Msg.id;
});
sse.addEventListener('message', (e) => {
console.log(`message: ${e.data}`);
msg = `message: ${e.data} ${Date.now()}`;
sse.addEventListener('msg', (e) => {
let Msg = JSON.parse(e.data);
msg = `Msg: ${Msg.id} ${Msg.data} ${now()}`;
console.log(msg);
})
return () => {
@ -62,11 +73,11 @@
await fetch('/api/msg');
}
async function ping() {
async function Ping() {
await fetch('/api/ping');
}
async function pong() {
async function Pong() {
await fetch('/api/pong');
}
@ -75,7 +86,21 @@
<h1>{msg}</h1>
<ul>
<li>{jmsg.event}</li>
<li>{jmsg.data}</li>
<li>{jmsg.id}</li>
</ul>
<br>
<h2>Ping: {dping}</h2>
<br>
<h2>Pong: {dpong}</h2>
<button on:click={newmsg}>send Msg</button>
<button on:click={ping}>send Ping</button>
<button on:click={pong}>send Pong</button>
<button on:click={Ping}>send Ping</button>
<button on:click={Pong}>send Pong</button>
<hr>
<button on:click={Subscribe}>SSE Msg</button>